propagate from branch 'im.pidgin.pidgin' (head 447ebf410d618d48003894dc348f9f0e1616e1e7)

Wed, 12 Sep 2007 19:11:38 +0000

author
Gabriel Schulhof <nix@go-nix.ca>
date
Wed, 12 Sep 2007 19:11:38 +0000
changeset 19997
24bc4ea43075
parent 19977
447ebf410d61 (diff)
parent 19480
3f641641f3d6 (current diff)
child 19998
1eb3dcc81314

propagate from branch 'im.pidgin.pidgin' (head 447ebf410d618d48003894dc348f9f0e1616e1e7)
to branch 'org.maemo.garage.pidgin.pidgin.dialog-transience' (head 3f641641f3d6a62f9e576bca75c065e619a97a29)

finch/gntblist.c file | annotate | diff | comparison | revisions
finch/gntplugin.c file | annotate | diff | comparison | revisions
finch/gntpounce.c file | annotate | diff | comparison | revisions
finch/gntprefs.c file | annotate | diff | comparison | revisions
finch/gntrequest.c file | annotate | diff | comparison | revisions
finch/gntstatus.c file | annotate | diff | comparison | revisions
libpurple/account.c file | annotate | diff | comparison | revisions
libpurple/conversation.c file | annotate | diff | comparison | revisions
libpurple/ft.c file | annotate | diff | comparison | revisions
libpurple/plugins/autoaccept.c file | annotate | diff | comparison | revisions
libpurple/plugins/buddynote.c file | annotate | diff | comparison | revisions
libpurple/plugins/idle.c file | annotate | diff | comparison | revisions
libpurple/plugins/offlinemsg.c file | annotate | diff | comparison | revisions
libpurple/protocols/gg/gg.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/auth.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/buddy.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/chat.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/jabber.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/presence.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/si.c file | annotate | diff | comparison | revisions
libpurple/protocols/jabber/xdata.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/dialog.c file | annotate | diff | comparison | revisions
libpurple/protocols/msn/msn.c file | annotate | diff | comparison | revisions
libpurple/protocols/novell/novell.c file | annotate | diff | comparison | revisions
libpurple/protocols/oscar/oscar.c file | annotate | diff | comparison | revisions
libpurple/protocols/oscar/peer.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/buddy_info.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/buddy_opt.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_im.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_join.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/group_opt.c file | annotate | diff | comparison | revisions
libpurple/protocols/qq/sys_msg.c file | annotate | diff | comparison | revisions
libpurple/protocols/sametime/sametime.c file | annotate | diff | comparison | revisions
libpurple/protocols/yahoo/yahoo.c file | annotate | diff | comparison | revisions
libpurple/request.c file | annotate | diff | comparison | revisions
libpurple/request.h file | annotate | diff | comparison | revisions
libpurple/server.c file | annotate | diff | comparison | revisions
pidgin/gtkaccount.c file | annotate | diff | comparison | revisions
pidgin/gtkblist.c file | annotate | diff | comparison | revisions
pidgin/gtkconv.c file | annotate | diff | comparison | revisions
pidgin/gtkdebug.c file | annotate | diff | comparison | revisions
pidgin/gtkdialogs.c file | annotate | diff | comparison | revisions
pidgin/gtkdialogs.h file | annotate | diff | comparison | revisions
pidgin/gtkimhtmltoolbar.c file | annotate | diff | comparison | revisions
pidgin/gtklog.c file | annotate | diff | comparison | revisions
pidgin/gtklog.h file | annotate | diff | comparison | revisions
pidgin/gtkplugin.c file | annotate | diff | comparison | revisions
pidgin/gtkplugin.h file | annotate | diff | comparison | revisions
pidgin/gtkpounce.c file | annotate | diff | comparison | revisions
pidgin/gtkpounce.h file | annotate | diff | comparison | revisions
pidgin/gtkprefs.c file | annotate | diff | comparison | revisions
pidgin/gtkprivacy.c file | annotate | diff | comparison | revisions
pidgin/gtkrequest.c file | annotate | diff | comparison | revisions
pidgin/gtksavedstatuses.c file | annotate | diff | comparison | revisions
pidgin/gtkstatusbox.c file | annotate | diff | comparison | revisions
pidgin/gtkutils.c file | annotate | diff | comparison | revisions
pidgin/pixmaps/emotes/default/24/mad-tongue.png file | annotate | diff | comparison | revisions
pidgin/pixmaps/emotes/default/24/madtongue.png file | annotate | diff | comparison | revisions
--- a/.mtn-ignore	Sun Aug 19 13:40:34 2007 +0000
+++ b/.mtn-ignore	Wed Sep 12 19:11:38 2007 +0000
@@ -1,3 +1,4 @@
+(.*/)?\.svn
 .*/?Makefile(\.in)?$
 (.*/)?TAGS$
 .*/?.*\.pc$
@@ -33,7 +34,7 @@
 pidgin-.*.tar.gz
 pidgin-.*.tar.bz2
 pidgin/pidgin$
-pidgin/pixmaps/emotes/default/22/theme
+pidgin/pixmaps/emotes/default/24/theme
 pidgin/pixmaps/emotes/none/theme
 pidgin/plugins/musicmessaging/music-messaging-bindings.c
 pidgin/plugins/perl/common/Makefile.PL$
--- a/AUTHORS	Sun Aug 19 13:40:34 2007 +0000
+++ b/AUTHORS	Wed Sep 12 19:11:38 2007 +0000
@@ -22,7 +22,7 @@
 Ka-Hing Cheung - Developer
 Sadrul Habib Chowdhury - Developer
 Mark 'KingAnt' Doliner - Developer
-Christian 'ChipX86' Hammond - Developer & Webmaster
+Casey Harkins - Developer
 Gary 'grim' Kramlich - Developer
 Richard 'rlaager' Laager - Developer
 Richard 'wabz' Nelson - Developer
@@ -31,21 +31,16 @@
 Etan 'deryni' Reisner - Developer
 Tim 'marv' Ringenbach - Developer
 Luke 'LSchiere' Schierer - Support
-Megan 'Cae' Schneider (support/QA)
+Megan 'Cae' Schneider - support/QA
 Evan Schoenberg - Developer
+Kevin 'SimGuy' Stange - Developer & Webmaster
 Stu 'nosnilmot' Tomlinson - Developer
 Nathan 'faceprint' Walp - Developer
 
 Crazy Patch Writers:
 -------------------
 John 'rekkanoryo' Bailey
-Felipe 'shx' Contreras
-Decklin Foster
-Casey Harkins
 Peter 'Bleeter' Lawler
-Robert 'Robot101' McQueen
-Benjamin Miller
-Kevin 'SimGuy' Stange
 
 Retired Developers:
 ------------------
@@ -53,11 +48,19 @@
 Jim Duchek <jim@linuxpimps.com> - maintainer
 Rob Flynn <gaim@robflynn.com> - maintainer
 Adam Fritzler - libfaim maintainer
+Christian 'ChipX86' Hammond - Developer & Webmaster
 Syd Logan - hacker and designated driver [lazy bum]
 Jim Seymour - XMPP developer
 Mark Spencer <markster@marko.net> - original author
 Eric Warmenhoven <eric@warmenhoven.org> - lead developer
 
+Retired Crazy Patch Writers:
+---------------------------
+Felipe 'shx' Contreras
+Decklin Foster
+Robert 'Robot101' McQueen
+Benjamin Miller
+
 Artists:
 -------
 Hylke Bons - Icons
--- a/COPYING	Sun Aug 19 13:40:34 2007 +0000
+++ b/COPYING	Wed Sep 12 19:11:38 2007 +0000
@@ -2,7 +2,7 @@
 		       Version 2, June 1991
 
  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+     51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -305,7 +305,7 @@
 
     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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 
 
 Also add information on how to contact you by electronic and paper mail.
--- a/COPYRIGHT	Sun Aug 19 13:40:34 2007 +0000
+++ b/COPYRIGHT	Wed Sep 12 19:11:38 2007 +0000
@@ -156,12 +156,14 @@
 Casey Harkins
 Andy Harrison
 Andrew Hart (arhart)
+Anders Hasselqvist
 Rene Hausleitner
 Will Hawkins
 G. Sumner Hayes
 Michael R. Head
 Nick Hebner
 Mike Heffner
+Justin Heiner
 Benjamin Herrenschmidt
 Fernando Herrera
 hjheins
@@ -214,6 +216,7 @@
 Nicolas Lichtmaier
 Wesley Lin
 Artem Litvinovich
+Josh Littlefield
 Syd Logan
 Lokheed
 Norberto Lopes
@@ -304,6 +307,7 @@
 Bob Rossi
 Jason Roth
 Jean-Francois Roy
+Peter Ruibal
 Sam S.
 Pradyumna Sampath
 Arvind Samptur
@@ -344,6 +348,7 @@
 Sony Computer Entertainment America, Inc.
 Andy Spencer
 Mark Spencer
+Peter Speybrouck
 Lex Spoon
 Chris Stafford
 Kevin Stange
@@ -370,6 +375,7 @@
 Warren Togami
 Stu Tomlinson
 Bill Tompkins
+Gal Topper
 Chris Toshok
 Ken Tossell
 Tom Tromey
--- a/ChangeLog	Sun Aug 19 13:40:34 2007 +0000
+++ b/ChangeLog	Wed Sep 12 19:11:38 2007 +0000
@@ -1,6 +1,21 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.1.1 (08/17/2007):
+version 2.2.0:
+	Libpurple:
+	* New protocol plugin: MySpaceIM (Jeff Connelly, Google Summer of
+	  Code)
+
+	Pidgin:
+	* Insert Horizontal Rules and Strikethrough text from toolbar
+	* Option to show protocol icons in the buddy list, from the
+	  Buddies > Show menu (Justin Heiner)
+	* Ability to build with native, non-X11 GTK+ on OSX (Anders
+   	  Hasselqvist)
+
+	Finch:
+	* Per-conversation mute and logging options (accessible from the menu)
+
+version 2.1.1 (08/20/2007):
 	Yahoo:
 	* Added an account action to open your inbox in the yahoo prpl.
 	* Added support for Unicode status messages in Yahoo.
--- a/ChangeLog.API	Sun Aug 19 13:40:34 2007 +0000
+++ b/ChangeLog.API	Wed Sep 12 19:11:38 2007 +0000
@@ -1,6 +1,57 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-Version 2.1.1 (08/17/2007):
+Version 2.2.0 (??/??/????):
+	libpurple:
+		Added:
+		* PURPLE_MESSAGE_INVISIBLE flag, which can be used by
+		  purple_conv_im_send_with_flags to send a message, but not display it
+		  in the conversation
+		* serv_send_attention(), serv_got_attention(), as well as send_attention 
+		  and attention_types in PurplePluginProtocolInfo. This new API is used
+		  for zapping in MySpaceIM, buzzing in Yahoo, and nudging in MSN.
+		* PurpleConvMessage structure to represent a message in a
+		  conversation. purple_conversation_message_get_sender,
+		  purple_conversation_message_get_message,
+		  purple_conversation_message_get_flags and
+		  purple_conversation_message_get_timestamp to get information about a
+		  PurpleConvMessage.
+		* purple_conversation_get_message_history() to retrieve a list of
+		  PurpleConvMessage's in a conversation, and
+		  purple_conversation_clear_message_history to clear the history.
+
+		Changed:
+		* purple_prefs_load is now called within purple_prefs_init.
+		  The UI no longer needs to call it.
+		* writing-im-msg now receives the conversation name as the who
+		  argument if the caller of purple_conversation_write didn't
+		  provide a value for who.
+
+	Pidgin:
+		Added:
+		* pidgin_set_accessible_relations, sets up label-for and labelled-by
+		  ATK relations (broken out from pidgin_set_accessible_label)
+		* pidgin_conv_attach_to_conversation, to reattach the Pidgin UI to a
+		  conversation
+		* conversation-hiding and conversation-displayed signals.
+
+		Changed:
+		* pidgin_conversations_fill_menu now also adds a separator and a 'Show
+		  All' item if there are more than one conversations in the list.
+
+	Finch:
+		Added:
+		* finch_sound_is_enabled
+		* The reserved field in the FinchConv is now used to store information
+		  about the conversation (using FinchConversationFlag)
+		* finch_account_dialog_show
+
+		libgnt:
+		* gnt_slider_set_small_step, gnt_slider_set_large_step to allow more
+		  fine tuned updates of a GntSlider
+		* gnt_util_parse_xhtml_to_textview to parse XHTML strings in a
+		  GntTextView (this works only if libxml2 is available)
+
+Version 2.1.1 (08/20/2007):
 	libpurple:
 		Changed:
 		* PurpleAccountUiOps.request_authorize's authorize_cb and
--- a/ChangeLog.win32	Sun Aug 19 13:40:34 2007 +0000
+++ b/ChangeLog.win32	Wed Sep 12 19:11:38 2007 +0000
@@ -1,5 +1,9 @@
-version 2.1.1 (08/17/2007):
-        * No changes
+version 2.2.0 (??/??/2007):
+	* Updated gtkspell to 2.0.11
+	* Upgrade SILC to use the 1.1.2 toolkit
+
+version 2.1.1 (08/20/2007):
+	* No changes
 
 version 2.1.0 (7/28/2007):
 	* Updated launcher application (pidgin.exe) to support portable mode
--- a/Makefile.mingw	Sun Aug 19 13:40:34 2007 +0000
+++ b/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -51,6 +51,7 @@
 	silc.dll \
 	silcclient.dll \
 	softokn3.dll \
+	smime3.dll \
 	ssl3.dll
 
 #build an expression for `find` to use to ignore the above files
--- a/NEWS	Sun Aug 19 13:40:34 2007 +0000
+++ b/NEWS	Wed Sep 12 19:11:38 2007 +0000
@@ -1,6 +1,6 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-2.1.1 (8/17/2007):
+2.1.1 (8/20/2007):
 	Sean: Continuing our schedule of frequent releases, Pidgin 2.1.1
 	is out. In it, we've addressed a lot of UI issues from our
 	experimental new changes introduced in 2.1.0, and gave a lot of 
--- a/configure.ac	Sun Aug 19 13:40:34 2007 +0000
+++ b/configure.ac	Wed Sep 12 19:11:38 2007 +0000
@@ -43,18 +43,18 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [1])
+m4_define([purple_lt_current], [2])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [1])
-m4_define([purple_micro_version], [1])
+m4_define([purple_minor_version], [2])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
 
-m4_define([gnt_lt_current], [1])
+m4_define([gnt_lt_current], [2])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [1])
+m4_define([gnt_minor_version], [2])
 m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
@@ -136,7 +136,7 @@
 	;;
 esac
 
-ALL_LINGUAS="af am ar az bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@Latn sv ta te th tr uk vi xh zh_CN zh_HK zh_TW"
+ALL_LINGUAS="af am ar az be@latin bg bn bs ca ca@valencia cs da de dz el en_AU en_CA en_GB eo es et eu fa fi fr gl gu he hi hu id it ja ka kn ko ku lt mk my_MM nb ne nl nn pa pl pt_BR pt ps ro ru sk sl sq sr sr@Latn sv ta te th tr uk vi xh zh_CN zh_HK zh_TW"
 AM_GLIB_GNU_GETTEXT
 
 dnl If we don't have msgfmt, then po/ is going to fail -- ensure that
@@ -275,6 +275,8 @@
 AC_SUBST(GLIB_CFLAGS)
 AC_SUBST(GLIB_LIBS)
 
+AC_ARG_WITH(x, [],
+	with_x="$withval", with_x="yes")
 AC_ARG_ENABLE(gtkui, [AC_HELP_STRING([--disable-gtkui],
 		[compile without GTK+ user interface])],
 	enable_gtkui="$enableval", enable_gtkui="yes")
@@ -309,7 +311,10 @@
 	[AC_HELP_STRING([--disable-cap],
 		[compile without Contact Availability Prediction plugin])],
 	enable_cap="$enableval", enable_cap="yes")
-
+AC_ARG_ENABLE(gestures,
+	[AC_HELP_STRING([--disable-gestures],
+		[compile without the gestures plugin])],
+	enable_gestures="$enableval", enable_gestures="yes")
 
 AC_PATH_XTRA
 # We can't assume that $x_libraries will be set, because autoconf does not
@@ -343,50 +348,79 @@
 			AC_DEFINE(HAVE_PANGO14, 1, [Define if we have Pango 1.4 or newer.]),:)
 
 	dnl #######################################################################
+	dnl # Check if we should compile with X support
+	dnl #######################################################################
+	if test "x$with_x" = "xyes" ; then
+		PKG_CHECK_MODULES(X11, x11,
+			[AC_DEFINE(HAVE_X, 1, [Define to 1 if you have X11])],
+			[AC_MSG_RESULT(no)
+			with_x=no])
+		AC_SUBST(X11_LIBS)
+		AC_SUBST(X11_CFLAGS)
+	fi
+	
+	dnl #######################################################################
 	dnl # Check for XScreenSaver
 	dnl #######################################################################
 	if test "x$enable_screensaver" = "xyes" ; then
-		old_LIBS="$LIBS"
-		LIBS="$LIBS $GTK_LIBS $x_libpath_add"
-		XSS_LIBS=""
-		XSS_HEADERS=""
-		AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm])
-		AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm])
-		if test "x$XSS_LIBS" != "x"; then
-			oldCPPFLAGS="$CPPFLAGS"
-			CPPFLAGS="$CPPFLAGS $x_incpath_add"
-			AC_TRY_COMPILE([
+		if test "x$with_x" = "xyes" ; then
+			old_LIBS="$LIBS"
+			LIBS="$LIBS $GTK_LIBS $x_libpath_add"
+			XSS_LIBS=""
+			XSS_HEADERS=""
+			AC_CHECK_LIB(Xext, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_EXTRA_LIBS"],[],[-lX11 -lXext -lm])
+			AC_CHECK_LIB(Xss, XScreenSaverRegister,[XSS_LIBS="$X_LIBS $X_PRE_LIBS -lX11 -lXext $X_LIBS $X_EXTRA_LIBS -lXss"],[],[-lX11 -lXext -lm])
+			if test "x$XSS_LIBS" != "x"; then
+				oldCPPFLAGS="$CPPFLAGS"
+				CPPFLAGS="$CPPFLAGS $x_incpath_add"
+				AC_TRY_COMPILE([
 					#include <X11/Xlib.h>
 					#include <X11/extensions/scrnsaver.h>
-				], [], [], [enable_screensaver=no])
-			CPPFLAGS="$oldCPPFLAGS"
+					], [], [], [enable_screensaver=no])
+				CPPFLAGS="$oldCPPFLAGS"
+			else
+				enable_screensaver=no
+			fi
+			LIBS="$old_LIBS"
+
+			if test "x$enable_screensaver" = "xyes" ; then
+				AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.])
+				AC_SUBST(XSS_LIBS)
+			fi
 		else
 			enable_screensaver=no
 		fi
-		LIBS="$old_LIBS"
-
-		if test "x$enable_screensaver" = "xyes" ; then
-			AC_DEFINE(USE_SCREENSAVER, 1, [Define if we're using XScreenSaver.])
-			AC_SUBST(XSS_LIBS)
-		fi
 	fi
 
 	dnl #######################################################################
 	dnl # Check for X session management libs
 	dnl #######################################################################
 	if test "x$enable_sm" = "xyes"; then
-		enable_sm=no
-		AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE])
-		if test "x$found_sm_lib" = "xtrue"; then
-			oldCPPFLAGS="$CPPFLAGS"
-			CPPFLAGS="$CPPFLAGS $x_incpath_add"
-			AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes)
-			CPPFLAGS="$oldCPPFLAGS"
+		if test "x$with_x" = "xyes" ; then
+			enable_sm=no
+			AC_CHECK_LIB(SM, SmcSaveYourselfDone, found_sm_lib=true, , [$x_libpath_add -lICE])
+			if test "x$found_sm_lib" = "xtrue"; then
+				oldCPPFLAGS="$CPPFLAGS"
+				CPPFLAGS="$CPPFLAGS $x_incpath_add"
+				AC_CHECK_HEADERS(X11/SM/SMlib.h, SM_LIBS="$x_libpath_add -lSM -lICE" enable_sm=yes)
+				CPPFLAGS="$oldCPPFLAGS"
+			fi
+
+			if test "x$enable_sm" = "xyes"; then
+				AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.])
+				AC_SUBST(SM_LIBS)
+			fi
+		else
+			enable_sm=no
 		fi
+	fi
 
-		if test "x$enable_sm" = "xyes"; then
-			AC_DEFINE(USE_SM, 1, [Define if we're using X Session Management.])
-			AC_SUBST(SM_LIBS)
+	dnl #######################################################################
+	dnl # Check for X11 to allow the gestures plugin
+	dnl #######################################################################
+	if test "x$enable_gestures" = "xyes"; then
+		if test "x$with_x" = "xno" ; then
+			enable_gestures=no
 		fi
 	fi
 
@@ -450,10 +484,11 @@
 	dnl #######################################################################
 	if test "x$enable_cap" = "xyes"; then
 		PKG_CHECK_MODULES(SQLITE3, sqlite3 >= 3.3,,[
-													AC_MSG_RESULT(no)
-													enable_cap="no"
-													])
+			AC_MSG_RESULT(no)
+			enable_cap="no"
+			])
 	fi
+        
 
 else # GTK
 	enable_cap=no
@@ -467,6 +502,8 @@
 AM_CONDITIONAL(ENABLE_GTK, test "x$enable_gtkui" = "xyes")
 AM_CONDITIONAL(BUILD_GEVOLUTION, test "x$enable_gevolution" = "xyes")
 AM_CONDITIONAL(ENABLE_CAP, test "x$enable_cap" = "xyes")
+AM_CONDITIONAL(ENABLE_GESTURES, test "x$enable_gestures" = "xyes")
+
 
 dnl #######################################################################
 dnl # Check for ncurses and other things used by the console UI
@@ -531,11 +568,6 @@
 			fi
 		fi
 	fi
-
-	PKG_CHECK_MODULES(X11, x11,
-		[AC_DEFINE(HAVE_X11, 1, [Define to 1 if you have X11])], [AC_MSG_RESULT(no)])
-	AC_SUBST(X11_LIBS)
-	AC_SUBST(X11_CFLAGS)
 fi
 
 AC_SUBST(GNT_LIBS)
@@ -858,7 +890,7 @@
 fi
 
 if test "x$STATIC_PRPLS" = "xall" ; then
-	STATIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr"
+	STATIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes" ; then
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'`
@@ -904,6 +936,7 @@
 		irc)		static_irc=yes ;;
 		jabber)		static_jabber=yes ;;
 		msn)		static_msn=yes ;;
+		myspace)	static_myspace=yes ;;
 		novell)		static_novell=yes ;;
 		oscar)		static_oscar=yes ;;
 		aim)		static_oscar=yes ;;
@@ -924,6 +957,7 @@
 AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes")
 AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes")
 AM_CONDITIONAL(STATIC_MSN, test "x$static_msn" = "xyes")
+AM_CONDITIONAL(STATIC_MYSPACE, test "x$static_myspace" = "xyes")
 AM_CONDITIONAL(STATIC_NOVELL, test "x$static_novell" = "xyes")
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
 AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
@@ -939,7 +973,7 @@
 
 AC_ARG_WITH(dynamic_prpls, [AC_HELP_STRING([--with-dynamic-prpls], [specify which protocols to build dynamically])], [DYNAMIC_PRPLS=`echo $withval | $sedpath 's/,/ /g'`])
 if test "x$DYNAMIC_PRPLS" = "xall" ; then
-	DYNAMIC_PRPLS="bonjour gg irc jabber msn novell oscar qq sametime silc simple yahoo zephyr"
+	DYNAMIC_PRPLS="bonjour gg irc jabber msn myspace novell oscar qq sametime silc simple yahoo zephyr"
 fi
 if test "x$have_meanwhile" != "xyes"; then
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'`
@@ -963,6 +997,7 @@
 		irc)		dynamic_irc=yes ;;
 		jabber)		dynamic_jabber=yes ;;
 		msn)		dynamic_msn=yes ;;
+		myspace)	dynamic_myspace=yes ;;
 		novell)		dynamic_novell=yes ;;
 		oscar)		dynamic_oscar=yes ;;
 		aim)		dynamic_oscar=yes ;;
@@ -983,6 +1018,7 @@
 AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
 AM_CONDITIONAL(DYNAMIC_MSN, test "x$dynamic_msn" = "xyes")
+AM_CONDITIONAL(DYNAMIC_MYSPACE, test "x$dynamic_myspace" = "xyes")
 AM_CONDITIONAL(DYNAMIC_NOVELL, test "x$dynamic_novell" = "xyes")
 AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes")
 AM_CONDITIONAL(DYNAMIC_QQ, test "x$dynamic_qq" = "xyes")
@@ -1151,7 +1187,7 @@
 		AC_PATH_PROG([PYTHON], [python], [no])
 	fi
 
-	if test x"$python" = x"no" ; then
+	if test x"$PYTHON" = x"no" ; then
 		AC_MSG_WARN([python interpreter not found in your path])
 		enable_dbus=no
 	fi
@@ -2137,6 +2173,7 @@
 		   pidgin/pixmaps/status/Makefile
 		   pidgin/pixmaps/status/11/Makefile
 		   pidgin/pixmaps/status/11/scalable/Makefile
+		   pidgin/pixmaps/status/11/rtl/Makefile
 		   pidgin/pixmaps/status/16/Makefile
 		   pidgin/pixmaps/status/16/rtl/Makefile
 		   pidgin/pixmaps/status/16/scalable/Makefile
@@ -2187,6 +2224,7 @@
 		   libpurple/protocols/irc/Makefile
 		   libpurple/protocols/jabber/Makefile
 		   libpurple/protocols/msn/Makefile
+		   libpurple/protocols/myspace/Makefile
 		   libpurple/protocols/novell/Makefile
 		   libpurple/protocols/null/Makefile
 		   libpurple/protocols/oscar/Makefile
@@ -2202,9 +2240,11 @@
 		   libpurple/version.h
 		   share/Makefile
 		   share/sounds/Makefile
+		   share/ca-certs/Makefile
 		   finch/Makefile
 		   finch/libgnt/Makefile
 		   finch/libgnt/gnt.pc
+		   finch/libgnt/pygnt/Makefile
 		   finch/libgnt/wms/Makefile
 		   finch/plugins/Makefile
 		   po/Makefile.in
@@ -2217,7 +2257,9 @@
 echo
 echo Build GTK+ 2.x UI............. : $enable_gtkui
 echo Build console UI.............. : $enable_consoleui
+echo Build for X11................. : $with_x
 echo
+echo Enable Gestures............... : $enable_gestures
 echo Protocols to build dynamically : $DYNAMIC_PRPLS
 echo Protocols to link statically.. : $STATIC_PRPLS
 echo
--- a/doc/Makefile.am	Sun Aug 19 13:40:34 2007 +0000
+++ b/doc/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -3,11 +3,13 @@
 EXTRA_DIST = \
 	C-HOWTO.dox \
 	PERL-HOWTO.dox \
+	SIGNAL-HOWTO.dox \
 	TCL-HOWTO.dox \
 	TracFooter.html \
 	TracHeader.html \
 	account-signals.dox \
 	blist-signals.dox \
+	certificate-signals.dox \
 	cipher-signals.dox \
 	connection-signals.dox \
 	conversation-signals.dox \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/SIGNAL-HOWTO.dox	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,137 @@
+/** @page signal-howto Signals HOWTO
+
+ @section Introduction
+  The libpurple signals interface is used for general event notification, such
+  as plugins being loaded or unloaded, allowing the GUI frontend to respond
+  appropriately to changing internal data. Unfortunately, its use is not at all
+  obvious from the information in the header files. This document uses code
+  snippets from the Pidgin/libpurple plugin systems to illustrate the proper
+  use of signals.
+
+ @section overview Overview of Signals
+  Signals in libpurple are very similar to those in GTK+. When certain events
+  happen, a named signal is "emitted" from a certain object. Emitting the
+  signal triggers a series of callbacks that have been "connected" to that
+  signal for that object. These callbacks take appropriate action in response
+  to the signal.
+
+ @section registering_signal Registering a Signal
+  The first step of using a signal is registering it with libpurple so that
+  callbacks may be connected to it. This is done using purple_signal_register()
+  Here is a slightly modified example from @c purple_plugins_init in 
+  @c libpurple/plugin.c :
+
+  @code
+	purple_signal_register( purple_plugins_get_handle(), /* Instance */
+				"plugin-load",               /* Signal name */
+				purple_marshal_VOID__POINTER,/* Marshal function */
+				NULL,                        /* Callback return value type */
+				1,                           /* Number of callback arguments (not including void *data) */
+				purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) /* Type of first callback argument */
+				);
+  @endcode
+
+  @subsection Instance
+  A reference to the object from which this signal is emitted, and to which
+  potential callbacks should be connected. In this case, it will be the entire
+  plugin module emitting the signal.
+  
+  @subsection signalname Signal Name
+  Unique identifier for the signal itself.
+
+  @subsection therest Callback function definition
+  The rest of the arguments specify the form of the callback function.
+
+  @subsubsection marshalfunc Marshal Function
+  @c purple_marshal_VOID__POINTER represents the callback function prototype,
+  not including a "data" argument, explained later. The form is 
+  @c purple_marshal_RETURNVALUETYPE__ARG1TYPE_ARG2TYPE_ETC. See signals.h for
+  more possible types.
+
+  In this case, the callback will have the form
+  @code
+	void cb(void *arg1, void *data)
+  @endcode
+
+  If @c purple_marshal_BOOLEAN__POINTER_POINTER_POINTER were specified, it
+  would be:
+  @code
+	gboolean cb(void *arg1, void *arg2, void *arg3, void *data)
+  @endcode
+
+  The @c void @c *data argument at the end of each callback function
+  provides the data argument given to purple_signal_connect() .
+
+  @subsubsection cb_ret_type Callback return value type
+  In our case, this is NULL, meaning "returns void".
+  @todo This could be described better.
+
+  @subsubsection num_args Number of arguments
+  The number of arguments (not including @c data ) that the callback function
+  will take.
+
+  @subsubsection type_arg Type of argument
+  @c purple_value_new(PURPLE_TYPE_SUBTYPE,PURPLE_SUBTYPE_PLUGIN) specifies that
+  the first argument given to the callback will be a @c PurplePlugin* . You
+  will need as many "type of argument" arguments to purple_signal_register() as
+  you specified in "Number of arguments" above.
+
+  @todo Describe this more.
+
+  @see value.h
+
+  @section connect Connecting to the signal
+  Once the signal is registered, you can connect callbacks to it. First, you
+  must define a callback function, such as this one from gtkplugin.c :
+  @code
+static void plugin_load_cb(PurplePlugin *plugin, gpointer data)
+{
+	GtkTreeView *view = (GtkTreeView *)data;
+	plugin_loading_common(plugin, view, TRUE);
+}
+  @endcode
+  Note that the callback function prototype matches that specified in the call
+  to purple_signal_register() above.
+
+  Once the callback function is defined, you can connect it to the signal.
+  Again from gtkplugin.c , in @c pidgin_plugin_dialog_show() :
+  @code
+	purple_signal_connect(purple_plugins_get_handle(), "plugin-load", /* What to connect to */
+		plugin_dialog, /* Object receiving the signal */
+		PURPLE_CALLBACK(plugin_load_cb), /* Callback function */
+		event_view, /* Data to pass to the callback function
+		);
+  @endcode
+
+  The first two arguments ("What to connect to") specify the object emitting
+  the signal (the plugin module) and what signal to listen for ("plugin-load").
+
+  The object receiving the signal is @c plugin_dialog , the Pidgin plugins
+  dialog. When @c plugin_dialog is deleted, then 
+  @c purple_signals_disconnect_by_handle(plugin_dialog) should be called to
+  remove all signal connections it is associated with.
+
+  The callback function is given using a helper macro, and finally the
+  @c data argument to be passed to @c plugin_load_cb is given as @c event_view,
+  a pointer to the GTK widget that @c plugin_load_cb needs to update.
+
+  @section emit-signal Emitting a signal
+  Connecting callbacks to signals is all well and good, but how do you "fire"
+  the signal and trigger the callback? At some point, you must "emit" the
+  signal, which immediately calls all connected callbacks.
+
+  As seen in @c purple_plugin_load() in plugin.c :
+  @code
+	purple_signal_emit(purple_plugins_get_handle(), "plugin-load", plugin);
+  @endcode
+  This causes the signal "plugin-load" to be emitted from the plugin module
+  (given by @c purple_plugins_get_handle() ), with the newly loaded plugin as
+  the argument to pass to any registered callback functions.
+
+  In our example, @c plugin_load_cb is called immediately as
+  @code
+	plugin_load_cb(plugin, event_view);
+  @endcode
+  and does whatever it does.
+  
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/certificate-signals.dox	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,31 @@
+/** @page certificate-signals Certificate Signals
+
+ @signals
+  @signal certificate-stored
+  @signal certificate-deleted
+ @endsignals
+
+ <hr>
+
+ @signaldef certificate-stored
+  @signalproto
+void (*certificate_stored)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool stores a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate has been stored into
+  @param id      Key the certificate was stored under
+ @endsignaldef
+
+ @signaldef certificate-deleted
+  @signalproto
+void (*certificate_deleted)(PurpleCertificatePool *pool, const gchar *id, gpointer data);
+  @endsignalproto
+  @signaldesc
+   Emitted when a pool deletes a certificate. Connect to the pool instance.
+  @param pool    Pool the certificate was deleted from
+  @param id      Key that was deleted
+ @endsignaldef
+
+ */
+// vim: syntax=c tw=75 et
--- a/doc/finch.1.in	Sun Aug 19 13:40:34 2007 +0000
+++ b/doc/finch.1.in	Wed Sep 12 19:11:38 2007 +0000
@@ -494,7 +494,7 @@
 
 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 
 .SH AUTHORS
 Sadrul Habib Chowdhury <\fIsadrul@users.sourceforge.net\fR>
--- a/doc/funniest_home_convos.txt	Sun Aug 19 13:40:34 2007 +0000
+++ b/doc/funniest_home_convos.txt	Wed Sep 12 19:11:38 2007 +0000
@@ -474,3 +474,16 @@
 
 19:23 <-- elb has quit (K-lined)
 
+19:01 <user> whoa
+19:01 <user> okay
+19:01 <user> now when i try to go into the left over files after the 
+             uninstall
+19:01 <user> something is seriously wrong because it says "the files on the 
+             c drive are not formatted, would you like to format?"
+19:03 <user> manually deleting the folder from command gives me a "Data 
+             error (cyclic redundancy check)."
+19:03 <elb> remember, one line per thought
+19:03 <elb> and yes, you have something wrong with your computer, we've 
+            established that
+19:03 <user> its functioning just fine
+
--- a/doc/gtkconv-signals.dox	Sun Aug 19 13:40:34 2007 +0000
+++ b/doc/gtkconv-signals.dox	Wed Sep 12 19:11:38 2007 +0000
@@ -8,6 +8,8 @@
   @signal displaying-chat-msg
   @signal displayed-chat-msg
   @signal conversation-switched
+  @signal conversation-hiding
+  @signal conversation-displayed
  @endsignals
 
  <hr>
@@ -116,5 +118,23 @@
   @param new_conv The now active conversation.
  @endsignaldef
 
+ @signaldef conversation-hiding
+  @signalproto
+void (*conversation_hiding)(PidginConversation *gtkconv);
+  @endsignalproto
+  @signaldesc
+   Emitted immediately before an existing conversation is hidden.
+  @param gtkconv  The PidginConversation
+ @endsignaldef
+
+ @signaldef conversation-displayed
+  @signalproto
+void (*conversation_displayed)(PidginConversation *gtkconv);
+  @endsignalproto
+  @signaldesc
+   Emitted right after the Pidgin UI is reattached to a conversation.
+  @param gtkconv  The PidginConversation
+ @endsignaldef
+
 */
 // vim: syntax=c tw=75 et
--- a/doc/pidgin.1.in	Sun Aug 19 13:40:34 2007 +0000
+++ b/doc/pidgin.1.in	Wed Sep 12 19:11:38 2007 +0000
@@ -526,7 +526,7 @@
 
 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 
 .SH AUTHORS
 Pidgin's active developers are:
--- a/finch/Makefile.am	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -14,6 +14,7 @@
 finch_SOURCES = \
 	gntaccount.c \
 	gntblist.c \
+	gntcertmgr.c \
 	gntconn.c \
 	gntconv.c \
 	gntdebug.c \
@@ -32,6 +33,7 @@
 finch_headers = \
 	gntaccount.h \
 	gntblist.h \
+	gntcertmgr.h \
 	gntconn.h \
 	gntconv.h \
 	gntdebug.h \
--- a/finch/finch.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/finch.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "finch.h"
 
@@ -360,9 +360,7 @@
 	purple_set_blist(purple_blist_new());
 	purple_blist_load();
 
-	/* TODO: Move prefs loading into purple_prefs_init() */
-	purple_prefs_load();
-	purple_prefs_update_old();
+	/* TODO: should this be moved into finch_prefs_init() ? */
 	finch_prefs_update_old();
 
 	/* load plugins we had when we quit */
@@ -412,6 +410,8 @@
 {
 	signal(SIGPIPE, SIG_IGN);
 
+	g_thread_init(NULL);
+
 	g_set_prgname("Finch");
 #if GLIB_CHECK_VERSION(2,2,0)
 	g_set_application_name(_("Finch"));
--- a/finch/finch.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/finch.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <glib.h>
 
--- a/finch/gntaccount.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntaccount.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
@@ -169,7 +169,7 @@
 	purple_account_set_remember_password(account,
 			gnt_check_box_get_checked(GNT_CHECK_BOX(dialog->remember)));
 	value = gnt_entry_get_text(GNT_ENTRY(dialog->password));
-	if (value && *value && purple_account_get_remember_password(account))
+	if (value && *value)
 		purple_account_set_password(account, value);
 	else
 		purple_account_set_password(account, NULL);
@@ -222,6 +222,11 @@
 
 	/* XXX: Proxy options */
 
+	if (accounts.window && accounts.tree) {
+		gnt_tree_set_selected(GNT_TREE(accounts.tree), account);
+		gnt_box_give_focus_to_child(GNT_BOX(accounts.window), accounts.tree);
+	}
+
 	gnt_widget_destroy(dialog->window);
 }
 
@@ -710,6 +715,11 @@
 	gnt_widget_show(accounts.window);
 }
 
+void finch_account_dialog_show(PurpleAccount *account)
+{
+	edit_account(account);
+}
+
 static gpointer
 finch_accounts_get_handle()
 {
--- a/finch/gntaccount.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntaccount.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_ACCOUNT_H
 #define _GNT_ACCOUNT_H
@@ -54,6 +54,13 @@
  */
 void finch_accounts_show_all(void);
 
+/**
+ * Show the edit dialog for an account.
+ *
+ * @param account  The account to edit, or @c NULL to create a new account.
+ */
+void finch_account_dialog_show(PurpleAccount *account);
+
 /*@}*/
 
 #endif
--- a/finch/gntblist.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntblist.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "finch.h"
 
@@ -2139,20 +2139,29 @@
 	}
 }
 
-static void
-account_signed_on_cb(PurpleConnection *pc, gpointer null)
+static gboolean
+auto_join_chats(gpointer data)
 {
 	PurpleBlistNode *node;
+	PurpleConnection *pc = data;
+	PurpleAccount *account = purple_connection_get_account(pc);
 
 	for (node = purple_blist_get_root(); node;
 			node = purple_blist_node_next(node, FALSE)) {
 		if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 			PurpleChat *chat = (PurpleChat*)node;
-			if (chat->account == purple_connection_get_account(pc) &&
+			if (chat->account == account &&
 					purple_blist_node_get_bool(node, "gnt-autojoin"))
 				serv_join_chat(purple_account_get_connection(chat->account), chat->components);
 		}
 	}
+	return FALSE;
+}
+
+static void
+account_signed_on_cb(PurpleConnection *gc, gpointer null)
+{
+	g_idle_add(auto_join_chats, gc);
 }
 
 static void toggle_pref_cb(GntMenuItem *item, gpointer n)
--- a/finch/gntblist.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntblist.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_BLIST_H
 #define _GNT_BLIST_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntcertmgr.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,340 @@
+/**
+ * @file gntcertmgr.c GNT Certificate Manager API
+ * @ingroup finch
+ *
+ * finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 "internal.h"
+
+#include "certificate.h"
+#include "debug.h"
+#include "notify.h"
+#include "request.h"
+
+#include "finch.h"
+#include "gntcertmgr.h"
+
+#include "gntbutton.h"
+#include "gntlabel.h"
+#include "gnttree.h"
+#include "gntutils.h"
+#include "gntwindow.h"
+
+struct {
+	GntWidget *window;
+	GntWidget *tree;
+	PurpleCertificatePool *pool;
+} certmgr;
+
+/* Pretty much Xerox of gtkcertmgr */
+
+/* Add certificate */
+static void
+tls_peers_mgmt_import_ok2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	const char *id = result;
+
+	/* TODO: Perhaps prompt if you're overwriting a cert? */
+
+	purple_certificate_pool_store(purple_certificate_find_pool("x509", "tls_peers"), id, crt);
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_cancel2_cb(gpointer data, const char *result)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+	purple_certificate_destroy(crt);
+}
+
+static void
+tls_peers_mgmt_import_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+
+	x509 = purple_certificate_pool_get_scheme(purple_certificate_find_pool("x509", "tls_peers"));
+
+	crt = purple_certificate_import(x509, filename);
+
+	if (crt != NULL) {
+		gchar *default_hostname;
+		default_hostname = purple_certificate_get_subject_name(crt);
+		purple_request_input(NULL,
+				_("Certificate Import"),
+				_("Specify a hostname"),
+				_("Type the host name this certificate is for."),
+				default_hostname, FALSE, FALSE, NULL,
+				_("OK"), G_CALLBACK(tls_peers_mgmt_import_ok2_cb),
+				_("Cancel"), G_CALLBACK(tls_peers_mgmt_import_cancel2_cb),
+				NULL, NULL, NULL,
+				crt);
+		g_free(default_hostname);
+	} else {
+		gchar * secondary;
+		secondary = g_strdup_printf(_("File %s could not be imported.\nMake sure that the file is readable and in PEM format.\n"), filename);
+		purple_notify_error(NULL,
+				_("Certificate Import Error"),
+				_("X.509 certificate import failed"),
+				secondary);
+		g_free(secondary);
+	}
+}
+
+static void
+add_cert_cb(GntWidget *button, gpointer null)
+{
+	purple_request_file(NULL,
+			_("Select a PEM certificate"),
+			"certificate.pem",
+			FALSE,
+			G_CALLBACK(tls_peers_mgmt_import_ok_cb),
+			NULL,
+			NULL, NULL, NULL, NULL );
+}
+
+/* Save certs in some file */
+static void
+tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename)
+{
+	PurpleCertificate *crt = (PurpleCertificate *) data;
+
+	if (!purple_certificate_export(filename, crt)) {
+		gchar * secondary;
+
+		secondary = g_strdup_printf(_("Export to file %s failed.\nCheck that you have write permission to the target path\n"), filename);
+		purple_notify_error(NULL,
+				    _("Certificate Export Error"),
+				    _("X.509 certificate export failed"),
+				    secondary);
+		g_free(secondary);
+	}
+
+	purple_certificate_destroy(crt);
+}
+
+static void
+save_cert_cb(GntWidget *button, gpointer null)
+{
+	PurpleCertificate *crt;
+	const char *key;
+
+	if (!certmgr.window)
+		return;
+
+	key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree));
+	if (!key)
+		return;
+
+	crt = purple_certificate_pool_retrieve(certmgr.pool, key);
+	if (!crt) {
+		purple_debug_error("gntcertmgr/tls_peers_mgmt",
+				"Id %s was not in the peers cache?!\n", key);
+		return;
+	}
+
+	purple_request_file((void*)key,
+			_("PEM X.509 Certificate Export"),
+			"certificate.pem", TRUE,
+			G_CALLBACK(tls_peers_mgmt_export_ok_cb),
+			G_CALLBACK(purple_certificate_destroy),
+			NULL, NULL, NULL,
+			crt);
+}
+
+/* Show information about a cert */
+static void
+info_cert_cb(GntWidget *button, gpointer null)
+{
+	const char *key;
+	PurpleCertificate *crt;
+	gchar *subject;
+	GByteArray *fpr_sha1;
+	gchar *fpr_sha1_asc;
+	gchar *primary, *secondary;
+
+	if (!certmgr.window)
+		return;
+
+	key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree));
+	if (!key)
+		return;
+
+	crt = purple_certificate_pool_retrieve(certmgr.pool, key);
+	g_return_if_fail(crt);
+
+	primary = g_strdup_printf(_("Certificate for %s"), key);
+
+	fpr_sha1 = purple_certificate_get_fingerprint_sha1(crt);
+	fpr_sha1_asc = purple_base16_encode_chunked(fpr_sha1->data,
+						    fpr_sha1->len);
+	subject = purple_certificate_get_subject_name(crt);
+
+	secondary = g_strdup_printf(_("Common name: %s\n\nSHA1 fingerprint:\n%s"), subject, fpr_sha1_asc);
+	
+	purple_notify_info(NULL,
+			   _("SSL Host Certificate"), primary, secondary);
+
+	g_free(primary);
+	g_free(secondary);
+	g_byte_array_free(fpr_sha1, TRUE);
+	g_free(fpr_sha1_asc);
+	g_free(subject);
+	purple_certificate_destroy(crt);
+}
+
+/* Delete a cert */
+static void
+tls_peers_mgmt_delete_confirm_cb(gchar *id, gint dontcare)
+{
+	if (!purple_certificate_pool_delete(certmgr.pool, id)) {
+		purple_debug_warning("gntcertmgr/tls_peers_mgmt",
+				"Deletion failed on id %s\n", id);
+	};
+
+	g_free(id);
+}
+
+static void
+delete_cert_cb(GntWidget *button, gpointer null)
+{
+	gchar *primary;
+	const char *key;
+
+	if (!certmgr.window)
+		return;
+
+	key = gnt_tree_get_selection_data(GNT_TREE(certmgr.tree));
+	if (!key)
+		return;
+
+	primary = g_strdup_printf(_("Really delete certificate for %s?"), key);
+
+	purple_request_close_with_handle((void *)key);
+	purple_request_yes_no((void *)key, _("Confirm certificate delete"),
+			primary, NULL,
+			2,
+			NULL, NULL, NULL,
+			g_strdup(key),
+			tls_peers_mgmt_delete_confirm_cb,
+			g_free);
+
+	g_free(primary);
+}
+
+/* populate the list */
+static void
+populate_cert_list()
+{
+	GList *idlist, *l;
+
+	if (!certmgr.window)
+		return;
+
+	gnt_tree_remove_all(GNT_TREE(certmgr.tree));
+
+	idlist = purple_certificate_pool_get_idlist(purple_certificate_find_pool("x509", "tls_peers"));
+	for (l = idlist; l; l = l->next) {
+		gnt_tree_add_row_last(GNT_TREE(certmgr.tree), g_strdup(l->data),
+				gnt_tree_create_row(GNT_TREE(certmgr.tree), l->data), NULL);
+	}
+	purple_certificate_pool_destroy_idlist(idlist);
+}
+
+static void
+cert_list_added(PurpleCertificatePool *pool, const char *id, gpointer null)
+{
+	g_return_if_fail(certmgr.window);
+	gnt_tree_add_row_last(GNT_TREE(certmgr.tree), g_strdup(id),
+			gnt_tree_create_row(GNT_TREE(certmgr.tree), id), NULL);
+}
+
+static void
+cert_list_removed(PurpleCertificatePool *pool, const char *id, gpointer null)
+{
+	g_return_if_fail(certmgr.window);
+	purple_request_close_with_handle((void*)id);
+	gnt_tree_remove(GNT_TREE(certmgr.tree), (void*)id);
+}
+
+void finch_certmgr_show(void)
+{
+	GntWidget *win, *tree, *box, *button;
+	PurpleCertificatePool *pool;
+
+	if (certmgr.window) {
+		gnt_window_present(certmgr.window);
+		return;
+	}
+
+	certmgr.window = win = gnt_vwindow_new(FALSE);
+	gnt_box_set_title(GNT_BOX(win), _("Certificate Manager"));
+	gnt_box_set_pad(GNT_BOX(win), 0);
+
+	certmgr.tree = tree = gnt_tree_new();
+	gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free);
+	gnt_tree_set_column_title(GNT_TREE(tree), 0, _("Hostname"));
+	gnt_tree_set_show_title(GNT_TREE(tree), TRUE);
+
+	gnt_box_add_widget(GNT_BOX(win), tree);
+
+	box = gnt_hbox_new(FALSE);
+	gnt_box_add_widget(GNT_BOX(win), box);
+
+	button = gnt_button_new(_("Add"));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(add_cert_cb), NULL);
+	gnt_util_set_trigger_widget(GNT_WIDGET(tree), GNT_KEY_INS, button);
+
+	button = gnt_button_new(_("Save"));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(save_cert_cb), NULL);
+
+	button = gnt_button_new(_("Info"));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(info_cert_cb), NULL);
+
+	button = gnt_button_new(_("Delete"));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(delete_cert_cb), NULL);
+	gnt_util_set_trigger_widget(GNT_WIDGET(tree), GNT_KEY_DEL, button);
+
+	button = gnt_button_new(_("Close"));
+	gnt_box_add_widget(GNT_BOX(box), button);
+	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gnt_widget_destroy), win);
+
+	g_signal_connect_swapped(G_OBJECT(win), "destroy", G_CALLBACK(g_nullify_pointer), &certmgr.window);
+
+	populate_cert_list();
+
+	pool = certmgr.pool = purple_certificate_find_pool("x509", "tls_peers");
+	purple_signal_connect(pool, "certificate-stored",
+			      win, PURPLE_CALLBACK(cert_list_added), NULL);
+	purple_signal_connect(pool, "certificate-deleted",
+			      win, PURPLE_CALLBACK(cert_list_removed), NULL);
+	g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(purple_signals_disconnect_by_handle), NULL);
+
+	gnt_widget_show(certmgr.window);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/gntcertmgr.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,31 @@
+/**
+ * @file gntcertmgr.h GNT Certificate Manager API
+ * @ingroup finch
+ *
+ * finch
+ *
+ * Finch is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ *
+ */
+#ifndef _GNT_CERTMGR_H
+#define _GNT_CERTMGR_H
+
+void finch_certmgr_show(void);
+
+#endif
--- a/finch/gntconn.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntconn.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "finch.h"
 
@@ -30,6 +30,7 @@
 #include "debug.h"
 #include "request.h"
 
+#include "gntaccount.h"
 #include "gntconn.h"
 
 #define INITIAL_RECON_DELAY_MIN  8000
@@ -87,6 +88,18 @@
 }
 
 static void
+ce_modify_account_cb(PurpleAccount *account)
+{
+	finch_account_dialog_show(account);
+}
+
+static void
+ce_enable_account_cb(PurpleAccount *account)
+{
+	purple_account_set_enabled(account, FINCH_UI, TRUE);
+}
+
+static void
 finch_connection_report_disconnect(PurpleConnection *gc, const char *text)
 {
 	FinchAutoRecon *info;
@@ -114,7 +127,13 @@
 		secondary = g_strdup_printf(_("%s\n\n"
 				"Finch will not attempt to reconnect the account until you "
 				"correct the error and re-enable the account."), text);
-		purple_notify_error(NULL, NULL, primary, secondary);
+
+		purple_request_action(account, NULL, primary, secondary, 2,
+							account, NULL, NULL,
+							account, 3,
+							_("OK"), NULL,
+							_("Modify Account"), PURPLE_CALLBACK(ce_modify_account_cb),
+							_("Re-enable Account"), PURPLE_CALLBACK(ce_enable_account_cb));
 
 		g_free(act);
 		g_free(primary);
--- a/finch/gntconn.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntconn.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_CONN_H
 #define _GNT_CONN_H
--- a/finch/gntconv.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntconv.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <string.h>
 
@@ -37,6 +37,7 @@
 #include "gntdebug.h"
 #include "gntplugin.h"
 #include "gntprefs.h"
+#include "gntsound.h"
 #include "gntstatus.h"
 #include "gntpounce.h"
 
@@ -142,6 +143,11 @@
 			}
 			g_free(error);
 		}
+		else if (!purple_account_is_connected(ggconv->active_conv->account))
+		{
+			purple_conversation_write(ggconv->active_conv, "", _("Message was not sent, because you are not signed on."),
+					PURPLE_MESSAGE_ERROR | PURPLE_MESSAGE_NO_LOG, time(NULL));
+		}
 		else
 		{
 			char *escape = g_markup_escape_text(text, -1);
@@ -342,6 +348,53 @@
 }
 
 static void
+toggle_logging_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	PurpleConversation *conv = fc->active_conv;
+	gboolean logging = gnt_menuitem_check_get_checked(GNT_MENU_ITEM_CHECK(item));
+	GList *iter;
+
+	if (logging == purple_conversation_is_logging(conv))
+		return;
+
+	/* Xerox */
+	if (logging) {
+		/* Enable logging first so the message below can be logged. */
+		purple_conversation_set_logging(conv, TRUE);
+
+		purple_conversation_write(conv, NULL,
+				_("Logging started. Future messages in this conversation will be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+	} else {
+		purple_conversation_write(conv, NULL,
+				_("Logging stopped. Future messages in this conversation will not be logged."),
+				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
+				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
+				time(NULL));
+
+		/* Disable the logging second, so that the above message can be logged. */
+		purple_conversation_set_logging(conv, FALSE);
+	}
+
+	/* Each conversation with the same person will have the same logging setting */
+	for (iter = fc->list; iter; iter = iter->next) {
+		if (iter->data == conv)
+			continue;
+		purple_conversation_set_logging(iter->data, logging);
+	}
+}
+
+static void
+toggle_sound_cb(GntMenuItem *item, gpointer ggconv)
+{
+	FinchConv *fc = ggconv;
+	fc->flags ^= FINCH_CONV_NO_SOUND;
+}
+
+static void
 send_to_cb(GntMenuItem *m, gpointer n)
 {
 	PurpleAccount *account = g_object_get_data(G_OBJECT(m), "purple_account");
@@ -447,6 +500,18 @@
 
 		generate_send_to_menu(ggc);
 	}
+
+	item = gnt_menuitem_check_new(_("Enable Logging"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			purple_conversation_is_logging(ggc->active_conv));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_logging_cb, ggc);
+
+	item = gnt_menuitem_check_new(_("Enable Sounds"));
+	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
+			!(ggc->flags & FINCH_CONV_NO_SOUND));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	gnt_menuitem_set_callback(item, toggle_sound_cb, ggc);
 }
 
 static void
@@ -492,6 +557,12 @@
 	else
 		ggc = g_new0(FinchConv, 1);
 
+	/* Each conversation with the same person will have the same logging setting */
+	if (ggc->list) {
+		purple_conversation_set_logging(conv,
+				purple_conversation_is_logging(ggc->list->data));
+	}
+
 	ggc->list = g_list_prepend(ggc->list, conv);
 	ggc->active_conv = conv;
 	conv->ui_data = ggc;
@@ -542,6 +613,7 @@
 		gnt_tree_set_col_width(GNT_TREE(tree), 0, 1);   /* The flag column */
 		gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_utf8_collate);
 		gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free);
+		gnt_tree_set_search_column(GNT_TREE(tree), 1);
 		GNT_WIDGET_SET_FLAGS(tree, GNT_WIDGET_NO_BORDER);
 		gnt_box_add_widget(GNT_BOX(hbox), ggc->tv);
 		gnt_box_add_widget(GNT_BOX(hbox), tree);
@@ -581,6 +653,9 @@
 		g_signal_connect(G_OBJECT(ggc->entry), "text_changed", G_CALLBACK(send_typing_notification), ggc);
 	}
 
+	if (!finch_sound_is_enabled())
+		ggc->flags |= FINCH_CONV_NO_SOUND;
+
 	gg_create_menu(ggc);
 
 	g_free(title);
@@ -825,6 +900,23 @@
 	gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags));
 }
 
+static void
+finch_conv_present(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_window_present(fc->window);
+}
+
+static gboolean
+finch_conv_has_focus(PurpleConversation *conv)
+{
+	FinchConv *fc = FINCH_CONV(conv);
+	if (fc && fc->window)
+		return gnt_widget_has_focus(fc->window);
+	return FALSE;
+}
+
 static PurpleConversationUiOps conv_ui_ops = 
 {
 	finch_create_conversation,
@@ -836,8 +928,8 @@
 	finch_chat_rename_user,
 	finch_chat_remove_users,
 	finch_chat_update_user,
-	NULL, /* present */
-	NULL, /* has_focus */
+	finch_conv_present, /* present */
+	finch_conv_has_focus, /* has_focus */
 	NULL, /* custom_smiley_add */
 	NULL, /* custom_smiley_write */
 	NULL, /* custom_smiley_close */
@@ -915,6 +1007,7 @@
 {
 	FinchConv *ggconv = conv->ui_data;
 	gnt_text_view_clear(GNT_TEXT_VIEW(ggconv->tv));
+	purple_conversation_clear_message_history(conv);
 	return PURPLE_CMD_STATUS_OK;
 }
 
--- a/finch/gntconv.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntconv.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_CONV_H
 #define _GNT_CONV_H
@@ -43,6 +43,11 @@
 typedef struct _FinchConvChat FinchConvChat;
 typedef struct _FinchConvIm FinchConvIm;
 
+typedef enum
+{
+	FINCH_CONV_NO_SOUND     = 1 << 0,
+} FinchConversationFlag;
+
 struct _FinchConv
 {
 	GList *list;
@@ -53,7 +58,7 @@
 	GntWidget *tv;            /* text-view */
 	GntWidget *menu;
 	GntWidget *info;
-	void *pad;
+	FinchConversationFlag flags;
 
 	union
 	{
--- a/finch/gntdebug.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntdebug.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
--- a/finch/gntdebug.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntdebug.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_DEBUG_H
 #define _GNT_DEBUG_H
--- a/finch/gntft.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntft.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
@@ -193,6 +193,8 @@
 	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(finch_xfer_dialog_destroy), NULL);
 	gnt_box_set_toplevel(GNT_BOX(window), TRUE);
 	gnt_box_set_title(GNT_BOX(window), _("File Transfers"));
+	gnt_box_set_fill(GNT_BOX(window), TRUE);
+	gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID);
 
 	xfer_dialog->tree = tree = gnt_tree_new_with_columns(NUM_COLUMNS);
 	gnt_tree_set_column_titles(GNT_TREE(tree), _("Progress"), _("Filename"), _("Size"), _("Speed"), _("Remaining"), _("Status"));
@@ -219,7 +221,7 @@
 					 G_CALLBACK(toggle_clear_finished_cb), NULL);
 	gnt_box_add_widget(GNT_BOX(window), checkbox);
 
-	bbox = gnt_hbox_new(TRUE);
+	bbox = gnt_hbox_new(FALSE);
 
 	xfer_dialog->remove_button = button = gnt_button_new(_("Remove"));
 	g_signal_connect(G_OBJECT(button), "activate",
@@ -425,8 +427,11 @@
 	g_free(remaining_str);
 	g_free(kbsec);
 	if (purple_xfer_is_completed(xfer)) {
+		char *msg = g_strdup_printf(_("The file was saved as %s."), purple_xfer_get_local_filename(xfer));
 		gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_STATUS, _("Finished"));
 		gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_REMAINING, _("Finished"));
+		purple_xfer_conversation_write(xfer, msg, FALSE);
+		g_free(msg);
 	} else {
 		gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_STATUS, _("Transferring"));
 	}
--- a/finch/gntft.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntft.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _FINCHFT_H_
 #define _FINCHFT_H_
--- a/finch/gntidle.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntidle.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/finch/gntidle.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntidle.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_IDLE_H_
 #define _GNT_IDLE_H_
--- a/finch/gntnotify.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntnotify.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
--- a/finch/gntnotify.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntnotify.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_NOTIFY_H
 #define _GNT_NOTIFY_H
--- a/finch/gntplugin.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntplugin.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
--- a/finch/gntplugin.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntplugin.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_PLUGIN_H
 #define _GNT_PLUGIN_H
--- a/finch/gntpounce.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntpounce.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include <gnt.h>
@@ -452,7 +452,7 @@
 
 	gnt_box_add_widget(GNT_BOX(window), gnt_line_new(FALSE));
 	/* Now the button box! */
-	bbox = gnt_hbox_new(TRUE);
+	bbox = gnt_hbox_new(FALSE);
 
 	/* Cancel button */
 	button = gnt_button_new(_("Cancel"));
@@ -613,6 +613,12 @@
 static void
 pounces_manager_add_cb(GntButton *button, gpointer user_data)
 {
+	if (purple_accounts_get_all() == NULL) {
+		purple_notify_error(NULL, _("Cannot create pounce"),
+				_("You do not have any accounts."),
+				_("You must create an account first before you can create a pounce."));
+		return;
+	}
 	finch_pounce_editor_show(NULL, NULL, NULL);
 }
 
@@ -622,7 +628,8 @@
 {
 	PouncesManager *dialog = user_data;
 	PurplePounce *pounce = gnt_tree_get_selection_data(GNT_TREE(dialog->tree));
-	finch_pounce_editor_show(NULL, NULL, pounce);
+	if (pounce)
+		finch_pounce_editor_show(NULL, NULL, pounce);
 }
 
 static void
@@ -645,6 +652,9 @@
 	char *buf;
 
 	pounce = (PurplePounce *)gnt_tree_get_selection_data(GNT_TREE(dialog->tree));
+	if (pounce == NULL)
+		return;
+
 	account = purple_pounce_get_pouncer(pounce);
 	pouncer = purple_account_get_username(account);
 	pouncee = purple_pounce_get_pouncee(pounce);
@@ -696,7 +706,7 @@
 	gnt_box_add_widget(GNT_BOX(win), tree);
 
 	/* Button box. */
-	bbox = gnt_hbox_new(TRUE);
+	bbox = gnt_hbox_new(FALSE);
 
 	/* Add button */
 	button = gnt_button_new(_("Add"));
--- a/finch/gntpounce.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntpounce.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _FINCHPOUNCE_H_
 #define _FINCHPOUNCE_H_
--- a/finch/gntprefs.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntprefs.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "finch.h"
 
--- a/finch/gntprefs.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntprefs.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_PREFS_H
 #define _GNT_PREFS_H
--- a/finch/gntrequest.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntrequest.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
@@ -198,7 +198,7 @@
 
 static void *
 finch_request_choice(const char *title, const char *primary,
-		const char *secondary, unsigned int default_value,
+		const char *secondary, int default_value,
 		const char *ok_text, GCallback ok_cb,
 		const char *cancel_text, GCallback cancel_cb,
 		PurpleAccount *account, const char *who, PurpleConversation *conv,
@@ -244,7 +244,7 @@
 
 static void*
 finch_request_action(const char *title, const char *primary,
-		const char *secondary, unsigned int default_value,
+		const char *secondary, int default_value,
 		PurpleAccount *account, const char *who, PurpleConversation *conv,
 		const char *ui_hint, void *user_data, size_t actioncount,
 		va_list actions)
@@ -368,6 +368,22 @@
 	purple_request_close(PURPLE_REQUEST_FIELDS, button);
 }
 
+static void
+update_selected_account(GntEntry *screenname, const char *start, const char *end,
+		GntComboBox *accountlist)
+{
+	GList *accounts = gnt_tree_get_rows(GNT_TREE(accountlist->dropdown));
+	const char *name = gnt_entry_get_text(screenname);
+	while (accounts) {
+		if (purple_find_buddy(accounts->data, name)) {
+			gnt_combo_box_set_selected(accountlist, accounts->data);
+			gnt_widget_draw(GNT_WIDGET(accountlist));
+			break;
+		}
+		accounts = accounts->next;
+	}
+}
+
 static void *
 finch_request_fields(const char *title, const char *primary,
 		const char *secondary, PurpleRequestFields *allfields,
@@ -378,6 +394,7 @@
 {
 	GntWidget *window, *box;
 	GList *grlist;
+	GntWidget *screenname = NULL, *accountlist = NULL;
 
 	window = setup_request_window(title, primary, secondary, PURPLE_REQUEST_FIELDS);
 
@@ -439,6 +456,7 @@
 						gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node));
 					}
 					gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE);
+					screenname = entry;
 				} else if (hint && !strcmp(hint, "group")) {
 					PurpleBlistNode *node;
 					for (node = purple_blist_get_root(); node; node = node->next) {
@@ -556,6 +574,7 @@
 						gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account);
 				}
 				gnt_widget_set_size(combo, 20, 3); /* ew */
+				accountlist = combo;
 			}
 			else
 			{
@@ -576,6 +595,10 @@
 	setup_default_callback(window, cancel_cb, userdata);
 	gnt_widget_show(window);
 
+	if (screenname && accountlist) {
+		g_signal_connect(screenname, "completion", G_CALLBACK(update_selected_account), accountlist);
+	}
+
 	g_object_set_data(G_OBJECT(window), "fields", allfields);
 	
 	return window;
--- a/finch/gntrequest.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntrequest.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_REQUEST_H
 #define _GNT_REQUEST_H
--- a/finch/gntsound.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntsound.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "finch.h"
@@ -40,6 +40,8 @@
 #include "sound.h"
 #include "util.h"
 
+#include "gntconv.h"
+
 #include "gntbox.h"
 #include "gntwindow.h"
 #include "gntcombobox.h"
@@ -173,7 +175,8 @@
 
 		has_focus = purple_conversation_has_focus(conv);
 
-		if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))
+		if ((gntconv->flags & FINCH_CONV_NO_SOUND) ||
+			(has_focus && !purple_prefs_get_bool(make_pref("/conv_focus"))))
 		{
 			return;
 		}
@@ -409,14 +412,14 @@
 	GError *err = NULL;
 
 	switch (GST_MESSAGE_TYPE (msg)) {
-	case GST_MESSAGE_EOS:
-		gst_element_set_state(play, GST_STATE_NULL);
-		gst_object_unref(GST_OBJECT(play));
-		break;
 	case GST_MESSAGE_ERROR:
 		gst_message_parse_error(msg, &err, NULL);
 		purple_debug_error("gstreamer", "%s\n", err->message);
 		g_error_free(err);
+		/* fall-through and clean up */
+	case GST_MESSAGE_EOS:
+		gst_element_set_state(play, GST_STATE_NULL);
+		gst_object_unref(GST_OBJECT(play));
 		break;
 	case GST_MESSAGE_WARNING:
 		gst_message_parse_warning(msg, &err, NULL);
@@ -670,28 +673,34 @@
 {
 	PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
 	FinchSoundEvent * event = &sounds[id];
-	char *enabled, *file, *tmpfile;
+	char *enabled, *file, *tmpfile, *volpref;
 	gboolean temp_value;
+	int volume;
 
 	enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
 			finch_sound_get_active_profile(), event->pref);
 	file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s",
 			finch_sound_get_active_profile(), event->pref);
+	volpref = g_strdup(make_pref("/volume"));
 
 	temp_value = purple_prefs_get_bool(enabled);
-	tmpfile = g_strdup(purple_prefs_get_string(file));
+	tmpfile = g_strdup(purple_prefs_get_path(file));
+	volume = purple_prefs_get_int(volpref);
 
-	purple_prefs_set_string(file, event->file);
+	purple_prefs_set_path(file, event->file);
 	if (!temp_value) purple_prefs_set_bool(enabled, TRUE);
+	purple_prefs_set_int(volpref, gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume)));
 
 	purple_sound_play_event(id, NULL);
 
 	if (!temp_value) purple_prefs_set_bool(enabled, FALSE);
-	purple_prefs_set_string(file, tmpfile);
+	purple_prefs_set_path(file, tmpfile);
+	purple_prefs_set_int(volpref, volume);
 
 	g_free(enabled);
 	g_free(file);
 	g_free(tmpfile);
+	g_free(volpref);
 }
 
 static void
@@ -990,6 +999,8 @@
 
 	pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0);
 	gnt_slider_set_step(GNT_SLIDER(slider), 5);
+	gnt_slider_set_small_step(GNT_SLIDER(slider), 1);
+	gnt_slider_set_large_step(GNT_SLIDER(slider), 20);
 	label = gnt_label_new("");
 	gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label));
 	gnt_box_set_pad(GNT_BOX(tmpbox), 1);
@@ -1053,7 +1064,22 @@
 	load_pref_window(finch_sound_get_active_profile());
 
 	gnt_widget_show(win);
-}	
+}
+
+gboolean finch_sound_is_enabled(void)
+{
+	const char *pref = make_pref("/method");
+	const char *method = purple_prefs_get_string(pref);
+
+	if (!method)
+		return FALSE;
+	if (strcmp(method, "nosound") == 0)
+		return FALSE;
+	if (purple_prefs_get_int(make_pref("/volume")) <= 0)
+		return FALSE;
+
+	return TRUE;
+}
 
 static PurpleSoundUiOps sound_ui_ops =
 {
--- a/finch/gntsound.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntsound.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_SOUND_H
 #define _GNT_SOUND_H
@@ -55,6 +55,14 @@
 GList *finch_sound_get_profiles(void);
 
 /**
+ * Determine whether any sound will be played or not.
+ *
+ * @return Returns FALSE if preference is set to 'No sound', or if volume is
+ *         set to zero.
+ */
+gboolean finch_sound_is_enabled(void);
+
+/**
  * Gets GNT sound UI ops.
  *
  * @return The UI operations structure.
--- a/finch/gntstatus.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntstatus.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <gnt.h>
 #include <gntbox.h>
@@ -299,6 +299,7 @@
 	{
 		purple_notify_error(edit, _("Error"), _("Invalid title"),
 				_("Please enter a non-empty title for the status."));
+		gnt_box_give_focus_to_child(GNT_BOX(edit->window), edit->title);
 		return;
 	}
 
@@ -307,6 +308,7 @@
 	{
 		purple_notify_error(edit, _("Error"), _("Duplicate title"),
 				_("Please enter a different title for the status."));
+		gnt_box_give_focus_to_child(GNT_BOX(edit->window), edit->title);
 		return;
 	}
 	
@@ -447,6 +449,7 @@
 		sub->window = window = gnt_vbox_new(FALSE);
 		gnt_box_set_toplevel(GNT_BOX(window), TRUE);
 		gnt_box_set_title(GNT_BOX(window), _("Substatus"));  /* XXX: a better title */
+		gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID);
 
 		box = gnt_hbox_new(FALSE);
 		gnt_box_add_widget(GNT_BOX(box), gnt_label_new(_("Account:")));
@@ -523,7 +526,7 @@
 	gnt_box_set_toplevel(GNT_BOX(window), TRUE);
 	gnt_box_set_title(GNT_BOX(window), _("Edit Status"));
 	gnt_box_set_fill(GNT_BOX(window), TRUE);
-	gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_LEFT);
+	gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID);
 	gnt_box_set_pad(GNT_BOX(window), 0);
 
 	edits = g_list_append(edits, edit);
--- a/finch/gntstatus.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntstatus.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_STATUS_H
 #define _GNT_STATUS_H
--- a/finch/gntui.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntui.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 
@@ -25,6 +25,7 @@
 
 #include "gntaccount.h"
 #include "gntblist.h"
+#include "gntcertmgr.h"
 #include "gntconn.h"
 #include "gntconv.h"
 #include "gntdebug.h"
@@ -81,6 +82,7 @@
 	gnt_register_action(_("Accounts"), finch_accounts_show_all);
 	gnt_register_action(_("Buddy List"), finch_blist_show);
 	gnt_register_action(_("Buddy Pounces"), finch_pounces_manager_show);
+	gnt_register_action(_("Certificates"), finch_certmgr_show);
 	gnt_register_action(_("Debug Window"), finch_debug_window_show);
 	gnt_register_action(_("File Transfers"), finch_xfer_dialog_show);
 	gnt_register_action(_("Plugins"), finch_plugins_show_all);
@@ -89,8 +91,6 @@
 	gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
 
 #ifdef STANDALONE
-
-	finch_plugins_save_loaded();
 }
 
 void gnt_ui_uninit()
--- a/finch/gntui.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/gntui.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _GNT_UI_H
 #define _GNT_UI_H
--- a/finch/libgnt/COPYING	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/COPYING	Wed Sep 12 19:11:38 2007 +0000
@@ -2,7 +2,7 @@
 		       Version 2, June 1991
 
  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+     51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -305,7 +305,7 @@
 
     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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 
 
 Also add information on how to contact you by electronic and paper mail.
--- a/finch/libgnt/configure.ac	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/configure.ac	Wed Sep 12 19:11:38 2007 +0000
@@ -24,9 +24,9 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [1])
+m4_define([gnt_lt_current], [2])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [1])
+m4_define([gnt_minor_version], [2])
 m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
--- a/finch/libgnt/gnt-skel.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnt-skel.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gnt-skel.h"
--- a/finch/libgnt/gnt-skel.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnt-skel.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_SKEL_H
--- a/finch/libgnt/gnt.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnt.h	Wed Sep 12 19:11:38 2007 +0000
@@ -27,7 +27,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <glib.h>
@@ -37,6 +37,15 @@
 #include "gntkeys.h"
 
 /**
+ * Get things to compile in Glib < 2.8
+ */
+#if !GLIB_CHECK_VERSION(2,8,0)
+	#define G_PARAM_STATIC_NAME  G_PARAM_PRIVATE
+	#define G_PARAM_STATIC_NICK  G_PARAM_PRIVATE
+	#define G_PARAM_STATIC_BLURB  G_PARAM_PRIVATE
+#endif
+
+/**
  * 
  */
 void gnt_init(void);
--- a/finch/libgnt/gntbindable.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbindable.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <string.h>
--- a/finch/libgnt/gntbindable.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbindable.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_BINDABLE_H
--- a/finch/libgnt/gntbox.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbox.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntbox.h"
--- a/finch/libgnt/gntbox.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbox.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_BOX_H
--- a/finch/libgnt/gntbutton.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbutton.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <stdlib.h>
--- a/finch/libgnt/gntbutton.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntbutton.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_BUTTON_H
--- a/finch/libgnt/gntcheckbox.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcheckbox.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntcheckbox.h"
--- a/finch/libgnt/gntcheckbox.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcheckbox.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_CHECK_BOX_H
--- a/finch/libgnt/gntclipboard.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntclipboard.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntclipboard.h"
--- a/finch/libgnt/gntclipboard.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntclipboard.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_CLIPBOARD_H
--- a/finch/libgnt/gntcolors.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcolors.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "config.h"
@@ -133,6 +133,7 @@
 		restore_colors();
 }
 
+#if GLIB_CHECK_VERSION(2,6,0)
 static int
 get_color(char *key)
 {
@@ -164,7 +165,6 @@
 	return color;
 }
 
-#if GLIB_CHECK_VERSION(2,6,0)
 void gnt_colors_parse(GKeyFile *kfile)
 {
 	GError *error = NULL;
--- a/finch/libgnt/gntcolors.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcolors.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_COLORS_H
--- a/finch/libgnt/gntcombobox.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcombobox.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntbox.h"
--- a/finch/libgnt/gntcombobox.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntcombobox.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_COMBO_BOX_H
--- a/finch/libgnt/gntentry.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntentry.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <ctype.h>
--- a/finch/libgnt/gntentry.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntentry.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_ENTRY_H
--- a/finch/libgnt/gntfilesel.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntfilesel.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntbutton.h"
@@ -200,7 +200,7 @@
 	const char *tmp;
 	tmp = sel->suggest ? sel->suggest :
 		(const char*)gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files));
-	old = g_strdup_printf("%s%s%s", sel->current, sel->current[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : "");
+	old = g_strdup_printf("%s%s%s", SAFE(sel->current), SAFE(sel->current)[1] ? G_DIR_SEPARATOR_S : "", tmp ? tmp : "");
 	gnt_entry_set_text(GNT_ENTRY(sel->location), old);
 	g_free(old);
 }
--- a/finch/libgnt/gntfilesel.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntfilesel.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_FILE_SEL_H
--- a/finch/libgnt/gntkeys.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntkeys.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntkeys.h"
--- a/finch/libgnt/gntkeys.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntkeys.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_KEYS_H
--- a/finch/libgnt/gntlabel.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntlabel.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntlabel.h"
--- a/finch/libgnt/gntlabel.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntlabel.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_LABEL_H
--- a/finch/libgnt/gntline.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntline.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntline.h"
--- a/finch/libgnt/gntline.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntline.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_LINE_H
--- a/finch/libgnt/gntmain.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmain.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #define _GNU_SOURCE
--- a/finch/libgnt/gntmenu.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenu.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntmenu.h"
@@ -221,8 +221,13 @@
 {
 	/* check for a trigger key */
 	GList *iter;
+	GList *find;
 	GList *nth = g_list_find(menu->list, gnt_tree_get_selection_data(GNT_TREE(menu)));
-	GList *find = find_item_with_trigger(nth->next, NULL, trigger);
+
+	if (nth == NULL)
+		return FALSE;
+		
+	find = find_item_with_trigger(nth->next, NULL, trigger);
 	if (!find)
 		find = find_item_with_trigger(menu->list, nth->next, trigger);
 	if (!find)
--- a/finch/libgnt/gntmenu.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenu.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_MENU_H
--- a/finch/libgnt/gntmenuitem.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenuitem.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntmenu.h"
--- a/finch/libgnt/gntmenuitem.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenuitem.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_MENUITEM_H
--- a/finch/libgnt/gntmenuitemcheck.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenuitemcheck.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntmenuitemcheck.h"
--- a/finch/libgnt/gntmenuitemcheck.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntmenuitemcheck.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_MENU_ITEM_CHECK_H
--- a/finch/libgnt/gntslider.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntslider.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntcolors.h"
@@ -125,22 +125,66 @@
 step_back(GntBindable *bindable, GList *null)
 {
 	GntSlider *slider = GNT_SLIDER(bindable);
-	if (slider->current <= slider->min)
-		return FALSE;
 	gnt_slider_advance_step(slider, -1);
 	return TRUE;
 }
 
 static gboolean
+small_step_back(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current - slider->smallstep);
+	return TRUE;
+}
+
+static gboolean
+large_step_back(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current - slider->largestep);
+	return TRUE;
+}
+
+static gboolean
 step_forward(GntBindable *bindable, GList *list)
 {
 	GntSlider *slider = GNT_SLIDER(bindable);
-	if (slider->current >= slider->max)
-		return FALSE;
 	gnt_slider_advance_step(slider, 1);
 	return TRUE;
 }
 
+static gboolean
+small_step_forward(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current + slider->smallstep);
+	return TRUE;
+}
+
+static gboolean
+large_step_forward(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->current + slider->largestep);
+	return TRUE;
+}
+
+static gboolean
+move_min_value(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->min);
+	return TRUE;
+}
+
+static gboolean
+move_max_value(GntBindable *bindable, GList *null)
+{
+	GntSlider *slider = GNT_SLIDER(bindable);
+	gnt_slider_set_value(slider, slider->max);
+	return TRUE;
+}
+
 static void
 gnt_slider_class_init(GntSliderClass *klass)
 {
@@ -165,8 +209,14 @@
 	gnt_bindable_register_binding(bindable, "step-backward", GNT_KEY_DOWN, NULL);
 	gnt_bindable_class_register_action(bindable, "step-forward", step_forward, GNT_KEY_RIGHT, NULL);
 	gnt_bindable_register_binding(bindable, "step-forward", GNT_KEY_UP, NULL);
-
-	/* XXX: how would home/end work? */
+	gnt_bindable_class_register_action(bindable, "small-step-backward", small_step_back, GNT_KEY_CTRL_LEFT, NULL);
+	gnt_bindable_register_binding(bindable, "small-step-backward", GNT_KEY_CTRL_DOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "small-step-forward", small_step_forward, GNT_KEY_CTRL_RIGHT, NULL);
+	gnt_bindable_register_binding(bindable, "small-step-forward", GNT_KEY_CTRL_UP, NULL);
+	gnt_bindable_class_register_action(bindable, "large-step-backward", large_step_back, GNT_KEY_PGDOWN, NULL);
+	gnt_bindable_class_register_action(bindable, "large-step-forward", large_step_forward, GNT_KEY_PGUP, NULL);
+	gnt_bindable_class_register_action(bindable, "min-value", move_min_value, GNT_KEY_HOME, NULL);
+	gnt_bindable_class_register_action(bindable, "max-value", move_max_value, GNT_KEY_END, NULL);
 
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 }
@@ -233,10 +283,14 @@
 
 void gnt_slider_set_value(GntSlider *slider, int value)
 {
+	int old;
 	if (slider->current == value)
 		return;
+	old = slider->current;
 	slider->current = value;
 	sanitize_value(slider);
+	if (old == slider->current)
+		return;
 	redraw_slider(slider);
 	slider_value_changed(slider);
 }
@@ -248,10 +302,7 @@
 
 int gnt_slider_advance_step(GntSlider *slider, int steps)
 {
-	slider->current += steps * slider->step;
-	sanitize_value(slider);
-	redraw_slider(slider);
-	slider_value_changed(slider);
+	gnt_slider_set_value(slider, slider->current + steps * slider->step);
 	return slider->current;
 }
 
@@ -260,6 +311,16 @@
 	slider->step = step;
 }
 
+void gnt_slider_set_small_step(GntSlider *slider, int step)
+{
+	slider->smallstep = step;
+}
+
+void gnt_slider_set_large_step(GntSlider *slider, int step)
+{
+	slider->largestep = step;
+}
+
 void gnt_slider_set_range(GntSlider *slider, int max, int min)
 {
 	slider->max = MAX(max, min);
--- a/finch/libgnt/gntslider.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntslider.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_SLIDER_H
@@ -56,6 +56,8 @@
 	int min;        /* minimum value */
 	int step;       /* amount to change at each step */
 	int current;    /* current value */
+	int smallstep;
+	int largestep;
 };
 
 struct _GntSliderClass
@@ -103,11 +105,27 @@
  * Sets the amount of change at each step.
  * 
  * @param slider  The slider
- * @param step    The amount for each ste
+ * @param step    The amount for each step
  */
 void gnt_slider_set_step(GntSlider *slider, int step);
 
 /**
+ * Sets the amount of change a small step.
+ * 
+ * @param slider  The slider
+ * @param step    The amount for a small step (for the slider)
+ */
+void gnt_slider_set_small_step(GntSlider *slider, int step);
+
+/**
+ * Sets the amount of change a large step.
+ * 
+ * @param slider  The slider
+ * @param step    The amount for a large step (for the slider)
+ */
+void gnt_slider_set_large_step(GntSlider *slider, int step);
+
+/**
  * Advance the slider forward or backward.
  *
  * @param slider   The slider
--- a/finch/libgnt/gntstyle.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntstyle.h"
@@ -55,6 +55,8 @@
 	if (!group)
 		group = "general";
 	return g_key_file_get_value(gkfile, group, key, NULL);
+#else
+	return NULL;
 #endif
 }
 
@@ -93,6 +95,7 @@
 	return def;
 }
 
+#if GLIB_CHECK_VERSION(2,6,0)
 static void
 refine(char *text)
 {
@@ -133,6 +136,7 @@
 {
 	return (char *)gnt_key_translate(key);
 }
+#endif
 
 void gnt_style_read_workspaces(GntWM *wm)
 {
--- a/finch/libgnt/gntstyle.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gnt.h"
--- a/finch/libgnt/gnttextview.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntstyle.h"
@@ -549,7 +549,8 @@
 		if ((end = strchr(start, '\r')) != NULL ||
 			(end = strchr(start, '\n')) != NULL) {
 			len = gnt_util_onscreen_width(start, end - has_scroll);
-			if (len >= widget->priv.width - line->length - has_scroll) {
+			if (widget->priv.width > 0 &&
+					len >= widget->priv.width - line->length - has_scroll) {
 				end = NULL;
 			}
 		}
--- a/finch/libgnt/gnttextview.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnttextview.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_TEXT_VIEW_H
--- a/finch/libgnt/gnttree.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnttree.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntmarshal.h"
@@ -985,11 +985,7 @@
 			g_param_spec_int("columns", "Columns",
 				"Number of columns in the tree.",
 				1, G_MAXINT, 1,
-#if GLIB_CHECK_VERSION(2,8,0)
 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
-#else
-				G_PARAM_READWRITE|G_PARAM_PRIVATE
-#endif
 			)
 		);
 
--- a/finch/libgnt/gnttree.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gnttree.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_TREE_H
--- a/finch/libgnt/gntutils.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntutils.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntbutton.h"
@@ -376,6 +376,101 @@
 #endif
 }
 
+#ifndef NO_LIBXML
+static void
+util_parse_html_to_tv(xmlNode *node, GntTextView *tv, GntTextFormatFlags flag)
+{
+	const char *name;
+	char *content;
+	xmlNode *ch;
+	gboolean processed = FALSE;
+	char *url = NULL;
+	gboolean insert_nl_s = FALSE, insert_nl_e = FALSE;
+
+	if (node == NULL || node->name == NULL || node->type != XML_ELEMENT_NODE)
+		return;
+
+	name = (char*)node->name;
+	if (g_ascii_strcasecmp(name, "b") == 0 ||
+		g_ascii_strcasecmp(name, "strong") == 0 ||
+		g_ascii_strcasecmp(name, "i") == 0 ||
+		g_ascii_strcasecmp(name, "blockquote") == 0) {
+		flag |= GNT_TEXT_FLAG_BOLD;
+	} else if (g_ascii_strcasecmp(name, "u") == 0) {
+		flag |= GNT_TEXT_FLAG_UNDERLINE;
+	} else if (g_ascii_strcasecmp(name, "br") == 0) {
+		insert_nl_e = TRUE;
+	} else if (g_ascii_strcasecmp(name, "a") == 0) {
+		flag |= GNT_TEXT_FLAG_UNDERLINE;
+		url = (char *)xmlGetProp(node, (xmlChar*)"href");
+	} else if (g_ascii_strcasecmp(name, "h1") == 0 ||
+			g_ascii_strcasecmp(name, "h2") == 0 ||
+			g_ascii_strcasecmp(name, "h3") == 0 ||
+			g_ascii_strcasecmp(name, "h4") == 0 ||
+			g_ascii_strcasecmp(name, "h5") == 0 ||
+			g_ascii_strcasecmp(name, "h6") == 0) {
+		insert_nl_s = TRUE;
+		insert_nl_e = TRUE;
+	} else if (g_ascii_strcasecmp(name, "title") == 0) {
+		insert_nl_s = TRUE;
+		insert_nl_e = TRUE;
+		flag |= GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_UNDERLINE;
+	} else {
+		/* XXX: Process other possible tags */
+	}
+
+	if (insert_nl_s)
+		gnt_text_view_append_text_with_flags(tv, "\n", flag);
+
+	for (ch = node->children; ch; ch = ch->next) {
+		if (ch->type == XML_ELEMENT_NODE) {
+			processed = TRUE;
+			util_parse_html_to_tv(ch, tv, flag);
+		}
+	}
+
+	if (!processed) {
+		content = (char*)xmlNodeGetContent(node);
+		gnt_text_view_append_text_with_flags(tv, content, flag);
+		xmlFree(content);
+	}
+
+	if (url) {
+		char *href = g_strdup_printf(" (%s)", url);
+		gnt_text_view_append_text_with_flags(tv, href, flag);
+		g_free(href);
+		xmlFree(url);
+	}
+
+	if (insert_nl_e)
+		gnt_text_view_append_text_with_flags(tv, "\n", flag);
+}
+#endif
+
+gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv)
+{
+#ifdef NO_LIBXML
+	return FALSE;
+#else
+	xmlParserCtxtPtr ctxt;
+	xmlDocPtr doc;
+	xmlNodePtr node;
+	GntTextFormatFlags flag = GNT_TEXT_FLAG_NORMAL;
+	gboolean ret = FALSE;
+
+	ctxt = xmlNewParserCtxt();
+	doc = xmlCtxtReadDoc(ctxt, (xmlChar*)string, NULL, NULL, XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
+	if (doc) {
+		node = xmlDocGetRootElement(doc);
+		util_parse_html_to_tv(node, tv, flag);
+		xmlFreeDoc(doc);
+		ret = TRUE;
+	}
+	xmlCleanupParser();
+	return ret;
+#endif
+}
+
 /* Setup trigger widget */
 typedef struct {
 	char *text;
@@ -408,4 +503,3 @@
 	g_signal_connect(G_OBJECT(wid), "key_pressed", G_CALLBACK(key_pressed), tb);
 	g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(free_trigger_button), tb);
 }
-
--- a/finch/libgnt/gntutils.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntutils.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,12 +21,13 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <glib.h>
 
 #include "gnt.h"
+#include "gnttextview.h"
 #include "gntwidget.h"
 
 typedef gpointer (*GDupFunc)(gconstpointer data);
@@ -132,6 +133,16 @@
 void gnt_util_parse_widgets(const char *string, int num, ...);
 
 /**
+ * Parse an XHTML string and add it in a GntTextView with
+ * appropriate text flags.
+ *
+ * @param string   The XHTML string
+ * @param tv       The GntTextView
+ * @return  @c TRUE if the string was added to the textview properly, @c FALSE otherwise.
+ */
+gboolean gnt_util_parse_xhtml_to_textview(const char *string, GntTextView *tv);
+
+/**
  * Make some keypress activate a button when some key is pressed with 'wid' in focus.
  *
  * @param widget  The widget
--- a/finch/libgnt/gntwidget.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwidget.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 /* Stuff brutally ripped from Gflib */
--- a/finch/libgnt/gntwidget.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwidget.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_WIDGET_H
--- a/finch/libgnt/gntwindow.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwindow.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "gntstyle.h"
--- a/finch/libgnt/gntwindow.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwindow.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNT_WINDOW_H
--- a/finch/libgnt/gntwm.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwm.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #define _GNU_SOURCE
@@ -738,7 +738,7 @@
 			print = ch;
 #ifndef NO_WIDECHAR
 			if (wch.chars[0] > 255) {
-				snprintf(unicode, sizeof(unicode), "&#x%x;", wch.chars[0]);
+				snprintf(unicode, sizeof(unicode), "&#x%x;", (unsigned int)wch.chars[0]);
 				print = unicode;
 			}
 #endif
--- a/finch/libgnt/gntwm.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntwm.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNTWM_H
--- a/finch/libgnt/gntws.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/gntws.h	Wed Sep 12 19:11:38 2007 +0000
@@ -21,7 +21,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef GNTWS_H
--- a/finch/libgnt/pygnt/dbus-gnt	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/dbus-gnt	Wed Sep 12 19:11:38 2007 +0000
@@ -13,7 +13,7 @@
 import gnt
 import sys
 
-from time import strftime
+import time
 
 convwins = {}
 
@@ -27,19 +27,26 @@
     # if a conv window is closed, then reopened, this thing crashes
     convwins[key] = None
 
-def wrote_msg(account, who, msg, conv, flags):
-    stuff = show_conversation(conv)
+def add_message(conv, who, msg, flags, timestamp):
+    stuff = show_conversation(conv, False)
     tv = stuff[1]
     tv.append_text_with_flags("\n", 0)
-    tv.append_text_with_flags(strftime("(%X) "), 8)
+    if timestamp:
+        tv.append_text_with_flags(time.strftime("(%X) ", time.localtime(timestamp)), 8)
+    else:
+        tv.append_text_with_flags(time.strftime("(%X) "), 8)
     if flags & 3:
         tv.append_text_with_flags(who + ": ", 1)
+        msg = purple.PurpleMarkupStripHtml(msg)
         tv.append_text_with_flags(msg, 0)
         stuff[0].set_urgent()
     else:
         tv.append_text_with_flags(msg, 8)
     tv.scroll(0)
 
+def wrote_msg(account, who, msg, conv, flags):
+    add_message(conv, who, msg, flags, None)
+
 bus = dbus.SessionBus()
 obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
 purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
@@ -79,7 +86,7 @@
 def conv_window_destroyed(win, key):
     del convwins[key]
 
-def show_conversation(conv):
+def show_conversation(conv, history):
     key = get_dict_key(conv)
     if key in convwins:
         return convwins[key]
@@ -96,9 +103,21 @@
     vbox.add_widget(entry)
     entry.connect("key_pressed", send_im_cb, conv)
     tv.clear()
+    tv.attach_scroll_widget(entry)
     win.show()
     convwins[key] = [win, tv, entry]
     win.connect("destroy", conv_window_destroyed, key)
+    
+    if history:
+        msgs = purple.PurpleConversationGetMessageHistory(conv)
+        msgs.reverse()
+        for msg in msgs:
+            who = purple.PurpleConversationMessageGetSender(msg)
+            what = purple.PurpleConversationMessageGetMessage(msg)
+            flags = purple.PurpleConversationMessageGetFlags(msg)
+            when = purple.PurpleConversationMessageGetTimestamp(msg)
+            add_message(conv, who, what, flags, when)
+
     return convwins[key]
 
 def show_buddylist():
@@ -127,7 +146,7 @@
 
 convs = purple.PurpleGetConversations()
 for conv in convs:
-    show_conversation(conv)
+    show_conversation(conv, True)
 
 gnt.gnt_main()
 
--- a/finch/libgnt/pygnt/example/rss/gnthtml.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/example/rss/gnthtml.py	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
 
 You should have received a copy of the GNU Lesser General Public
 License along with this application; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
 USA
 """
 
--- a/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
 
 You should have received a copy of the GNU Lesser General Public
 License along with this application; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
 USA
 """
 
@@ -54,7 +54,7 @@
     }
 
     __gntbindings__ = {
-        'jump-next-unread' : ('jump_next_unread', 'J')
+        'jump-next-unread' : ('jump_next_unread', 'n')
     }
 
     def jump_next_unread(self, null):
--- a/finch/libgnt/pygnt/example/rss/gntrss.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss.py	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
 
 You should have received a copy of the GNU Lesser General Public
 License along with this application; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
 USA
 """
 
@@ -61,6 +61,9 @@
         self.parent = parent
         self.unread = True
 
+    def __del__(self):
+        pass
+
     def remove(self):
         self.emit('delete', self.parent)
         if self.unread:
@@ -126,6 +129,9 @@
         self.pending = False
         self._refresh = {'time' : 30, 'id' : 0}
 
+    def __del__(self):
+        pass
+
     def do_set_property(self, property, value):
         if property.name == 'link':
             self.link = value
--- a/finch/libgnt/pygnt/gendef.sh	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/gendef.sh	Wed Sep 12 19:11:38 2007 +0000
@@ -31,7 +31,9 @@
 rm -f gnt.def
 for file in $FILES
 do
+	echo -n "Generating definitions for ${file} ... "
 	python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def
+	echo "Done"
 done
 
 # Remove the definitions about the enums
--- a/finch/libgnt/pygnt/gntbox.override	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/gntbox.override	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
  * USA
  */
 %%
--- a/finch/libgnt/pygnt/gntfilesel.override	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/gntfilesel.override	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
  * USA
  */
 %%
--- a/finch/libgnt/pygnt/gnttree.override	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/gnttree.override	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
  * USA
  */
 %%
@@ -51,7 +51,6 @@
 	while (list) {
 		PyObject *obj = list->data;
 		PyList_Append(py_list, obj);
-		Py_DECREF(obj);
 		list = list->next;
 	}
 	return py_list;
@@ -89,6 +88,11 @@
 		Py_DECREF(item);
 	}
 
+	if (parent == Py_None)
+		parent = NULL;
+	if (bigbro == Py_None)
+		bigbro = NULL;
+
 	list = g_list_reverse(list);
 	row = gnt_tree_create_row_from_list(GNT_TREE(self->obj), list);
 	gnt_tree_add_row_after(GNT_TREE(self->obj),
--- a/finch/libgnt/pygnt/gntwidget.override	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/gntwidget.override	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301
  * USA
  */
 %%
--- a/finch/libgnt/pygnt/test.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/pygnt/test.py	Wed Sep 12 19:11:38 2007 +0000
@@ -16,6 +16,9 @@
         self.__gobject_init__()
         self.set_property(type, value)
 
+    def __del__(self):
+        pass
+
     def do_set_property(self, pspec, value):
         if pspec.name == 'string':
             self.string = value
--- a/finch/libgnt/test/tv.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/libgnt/test/tv.c	Wed Sep 12 19:11:38 2007 +0000
@@ -5,6 +5,7 @@
 #include "gntbox.h"
 #include "gntentry.h"
 #include "gnttextview.h"
+#include "gntutils.h"
 
 static gboolean
 key_pressed(GntWidget *w, const char *key, GntWidget *view)
@@ -117,6 +118,8 @@
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "plugins: ", GNT_TEXT_FLAG_BOLD);
 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(view), "this is the 4th line\n", GNT_TEXT_FLAG_NORMAL);
 
+	gnt_util_parse_xhtml_to_textview("<p><b>Ohoy hoy!!</b><br/><p>I think this is going to</p> <u> WORK!!! </u><a href='www.google.com'>check this out!!</a></p>", GNT_TEXT_VIEW(view));
+
 #ifdef STANDALONE
 	gnt_main();
 
--- a/finch/plugins/gntclipboard.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/plugins/gntclipboard.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
@@ -38,10 +38,12 @@
 #include <plugin.h>
 #include <version.h>
 #include <debug.h>
+#include <notify.h>
 #include <gntwm.h>
 
 #include <gntplugin.h>
 
+#ifdef HAVE_X11
 static pid_t child = 0;
 
 static gulong sig_handle;
@@ -49,7 +51,6 @@
 static void
 set_clip(gchar *string)
 {
-#ifdef HAVE_X11
 	Window w;
 	XEvent e, respond;
 	XSelectionRequestEvent *req;
@@ -89,14 +90,12 @@
 			return;
 		}
 	}
-#endif
 	return;
 }
 
 static void
 clipboard_changed(GntWM *wm, gchar *string)
 {
-#ifdef HAVE_X11
 	if (child) {
 		kill(child, SIGTERM);
 	}
@@ -104,8 +103,8 @@
 		set_clip(string);
 		_exit(0);
 	}
+}
 #endif
-}
 
 static gboolean
 plugin_load(PurplePlugin *plugin)
@@ -113,25 +112,35 @@
 #ifdef HAVE_X11
 	if (!XOpenDisplay(NULL)) {
 		purple_debug_warning("gntclipboard", "Couldn't find X display\n");
+		purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+				_("Couldn't find X display"));
 		return FALSE;
 	}
-#endif
 	if (!getenv("WINDOWID")) {
 		purple_debug_warning("gntclipboard", "Couldn't find window\n");
+		purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+				_("Couldn't find window"));
 		return FALSE;
 	}
 	sig_handle = g_signal_connect(G_OBJECT(gnt_get_clipboard()), "clipboard_changed", G_CALLBACK(clipboard_changed), NULL);
 	return TRUE;
+#else
+	purple_notify_error(NULL, _("Error"), _("Error loading the plugin."),
+			_("This plugin cannot be loaded because it was not built with X11 support."));
+	return FALSE;
+#endif
 }
 
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
+#ifdef HAVE_X11
 	if (child) {
 		kill(child, SIGTERM);
 		child = 0;
 	}
 	g_signal_handler_disconnect(G_OBJECT(gnt_get_clipboard()), sig_handle);
+#endif
 	return TRUE;
 }
 
--- a/finch/plugins/gntgf.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/plugins/gntgf.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/finch/plugins/gnthistory.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/plugins/gnthistory.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 /* Ripped from gtk/plugins/history.c */
--- a/finch/plugins/lastlog.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/finch/plugins/lastlog.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/Makefile.am	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -36,6 +36,7 @@
 	accountopt.c \
 	blist.c \
 	buddyicon.c \
+	certificate.c \
 	cipher.c \
 	circbuffer.c \
 	cmds.c \
@@ -85,6 +86,7 @@
 	accountopt.h \
 	blist.h \
 	buddyicon.h \
+	certificate.h \
 	cipher.h \
 	circbuffer.h \
 	cmds.h \
--- a/libpurple/Makefile.mingw	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -33,6 +33,7 @@
 			accountopt.c \
 			blist.c \
 			buddyicon.c \
+			certificate.c \
 			cipher.c \
 			cmds.c \
 			connection.c \
--- a/libpurple/account.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/account.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "account.h"
@@ -913,6 +913,15 @@
 }
 
 void
+purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data)
+{
+	g_return_if_fail(account != NULL);
+	
+	account->registration_cb = cb;
+	account->registration_cb_user_data = user_data;
+}
+
+void
 purple_account_register(PurpleAccount *account)
 {
 	g_return_if_fail(account != NULL);
@@ -923,6 +932,17 @@
 	purple_connection_new(account, TRUE, purple_account_get_password(account));
 }
 
+void
+purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data)
+{
+	g_return_if_fail(account != NULL);
+	
+	purple_debug_info("account", "Unregistering account %s\n",
+					  purple_account_get_username(account));
+	
+	purple_connection_new_unregister(account, purple_account_get_password(account), cb, user_data);
+}
+
 static void
 request_password_ok_cb(PurpleAccount *account, PurpleRequestFields *fields)
 {
@@ -1720,15 +1740,6 @@
 purple_account_get_protocol_id(const PurpleAccount *account)
 {
 	g_return_val_if_fail(account != NULL, NULL);
-	/*
-	 * HACK by Seanegan
-	 */
-	if (!strcmp(account->protocol_id, "prpl-oscar")) {
-		if (isdigit(account->username[0]))
-			return "prpl-icq";
-		else
-			return "prpl-aim";
-	}
 	return account->protocol_id;
 }
 
--- a/libpurple/account.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/account.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref account-signals
  */
@@ -36,6 +36,8 @@
 
 typedef gboolean (*PurpleFilterAccountFunc)(PurpleAccount *account);
 typedef void (*PurpleAccountRequestAuthorizationCb)(void *);
+typedef void (*PurpleAccountRegistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data);
+typedef void (*PurpleAccountUnregistrationCb)(PurpleAccount *account, gboolean succeeded, void *user_data);
 
 #include "connection.h"
 #include "log.h"
@@ -136,6 +138,8 @@
 	PurpleLog *system_log;        /**< The system log                         */
 
 	void *ui_data;              /**< The UI can put data here.              */
+	PurpleAccountRegistrationCb registration_cb;
+	void *registration_cb_user_data;
 };
 
 #ifdef __cplusplus
@@ -172,6 +176,15 @@
 void purple_account_connect(PurpleAccount *account);
 
 /**
+ * Sets the callback for successful registration.
+ *
+ * @param account	The account for which this callback should be used
+ * @param cb	The callback
+ * @param user_data	The user data passed to the callback
+ */
+void purple_account_set_register_callback(PurpleAccount *account, PurpleAccountRegistrationCb cb, void *user_data);
+
+/**
  * Registers an account.
  *
  * @param account The account to register.
@@ -179,6 +192,15 @@
 void purple_account_register(PurpleAccount *account);
 
 /**
+ * Unregisters an account (deleting it from the server).
+ *
+ * @param account The account to unregister.
+ * @param cb Optional callback to be called when unregistration is complete
+ * @param user_data user data to pass to the callback
+ */
+void purple_account_unregister(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data);
+
+/**
  * Disconnects from an account.
  *
  * @param account The account to disconnect from.
--- a/libpurple/accountopt.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/accountopt.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 
--- a/libpurple/accountopt.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/accountopt.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_ACCOUNTOPT_H_
 #define _PURPLE_ACCOUNTOPT_H_
--- a/libpurple/blist.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/blist.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -1190,6 +1190,12 @@
 			group = purple_group_new(_("Chats"));
 			purple_blist_add_group(group,
 					purple_blist_get_last_sibling(purplebuddylist->root));
+		} else {
+			/* Add group to blist if isn't already on it. Fixes #2752. */
+			if (!purple_find_group(group->name)) {
+				purple_blist_add_group(group,
+						purple_blist_get_last_sibling(purplebuddylist->root));
+			}
 		}
 	} else {
 		group = (PurpleGroup*)node->parent;
@@ -1284,6 +1290,12 @@
 		g = (PurpleGroup *)((PurpleBlistNode *)c)->parent;
 	} else {
 		if (group) {
+			/* Add chat to blist if isn't already on it. Fixes #2752. */
+			if (!purple_find_group(group->name)) {
+				purple_blist_add_group(group,
+						purple_blist_get_last_sibling(purplebuddylist->root));
+			}
+
 			g = group;
 		} else {
 			g = purple_group_new(_("Buddies"));
--- a/libpurple/blist.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/blist.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref blist-signals
  */
--- a/libpurple/buddyicon.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/buddyicon.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "buddyicon.h"
--- a/libpurple/buddyicon.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/buddyicon.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_BUDDYICON_H_
 #define _PURPLE_BUDDYICON_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,1903 @@
+/**
+ * @file certificate.c Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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 <glib.h>
+
+#include "internal.h"
+#include "certificate.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "request.h"
+#include "signals.h"
+#include "util.h"
+
+/** List holding pointers to all registered certificate schemes */
+static GList *cert_schemes = NULL;
+/** List of registered Verifiers */
+static GList *cert_verifiers = NULL;
+/** List of registered Pools */
+static GList *cert_pools = NULL;
+
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificateScheme *scheme;
+	
+	g_return_if_fail(subject_name != NULL);
+	/* If you don't have a cert to check, why are you requesting that it
+	   be verified? */
+	g_return_if_fail(cert_chain != NULL);
+	g_return_if_fail(cb != NULL);
+
+	/* Look up the CertificateScheme */
+	scheme = purple_certificate_find_scheme(verifier->scheme_name);
+	g_return_if_fail(scheme);
+
+	/* Check that at least the first cert in the chain matches the
+	   Verifier scheme */
+	g_return_if_fail(scheme ==
+			 ((PurpleCertificate *) (cert_chain->data))->scheme);
+
+	/* Construct and fill in the request fields */
+	vrq = g_new0(PurpleCertificateVerificationRequest, 1);
+	vrq->verifier = verifier;
+	vrq->scheme = scheme;
+	vrq->subject_name = g_strdup(subject_name);
+	vrq->cert_chain = purple_certificate_copy_list(cert_chain);
+	vrq->cb = cb;
+	vrq->cb_data = cb_data;
+
+	/* Initiate verification */
+	(verifier->start_verification)(vrq);
+}
+
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st)
+{
+	PurpleCertificateVerifier *vr;
+
+	g_return_if_fail(vrq);
+
+	/* Pass the results on to the request's callback */
+	(vrq->cb)(st, vrq->cb_data);
+
+	/* And now to eliminate the request */
+	/* Fetch the Verifier responsible... */
+	vr = vrq->verifier;
+	/* ...and order it to KILL */
+	(vr->destroy_request)(vrq);
+
+	/* Now the internals have been cleaned up, so clean up the libpurple-
+	   created elements */
+	g_free(vrq->subject_name);
+	purple_certificate_destroy_list(vrq->cert_chain);
+
+	/*  A structure born
+	 *          to much ado
+	 *                   and with so much within.
+	 * It reaches now
+	 *             its quiet end. */
+	g_free(vrq);
+}
+
+
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
+
+	return (crt->scheme->copy_certificate)(crt);
+}
+
+GList *
+purple_certificate_copy_list(GList *crt_list)
+{
+	GList *new, *l;
+
+	/* First, make a shallow copy of the list */
+	new = g_list_copy(crt_list);
+
+	/* Now go through and actually duplicate each certificate */
+	for (l = new; l; l = l->next) {
+		l->data = purple_certificate_copy(l->data);
+	}
+
+	return new;
+}
+
+void
+purple_certificate_destroy (PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	
+	if (NULL == crt) return;
+
+	scheme = crt->scheme;
+
+	(scheme->destroy_certificate)(crt);
+}
+
+void
+purple_certificate_destroy_list (GList * crt_list)
+{
+	PurpleCertificate *crt;
+	GList *l;
+
+	for (l=crt_list; l; l = l->next) {
+		crt = (PurpleCertificate *) l->data;
+		purple_certificate_destroy(crt);
+	}
+
+	g_list_free(crt_list);
+}
+
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme, FALSE);
+	/* We can't compare two certs of unrelated schemes, obviously */
+	g_return_val_if_fail(issuer->scheme == scheme, FALSE);
+
+	return (scheme->signed_by)(crt, issuer);
+}
+
+gboolean
+purple_certificate_check_signature_chain(GList *chain)
+{
+	GList *cur;
+	PurpleCertificate *crt, *issuer;
+	gchar *uid;
+
+	g_return_val_if_fail(chain, FALSE);
+
+	uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
+	purple_debug_info("certificate",
+			  "Checking signature chain for uid=%s\n",
+			  uid);
+	g_free(uid);
+	
+	/* If this is a single-certificate chain, say that it is valid */
+	if (chain->next == NULL) {
+		purple_debug_info("certificate",
+				  "...Singleton. We'll say it's valid.\n");
+		return TRUE;
+	}
+
+	/* Load crt with the first certificate */
+	crt = (PurpleCertificate *)(chain->data);
+	/* And start with the second certificate in the chain */
+	for ( cur = chain->next; cur; cur = cur->next ) {
+		
+		issuer = (PurpleCertificate *)(cur->data);
+		
+		/* Check the signature for this link */
+		if (! purple_certificate_signed_by(crt, issuer) ) {
+			uid = purple_certificate_get_unique_id(issuer);
+			purple_debug_info("certificate",
+					  "...Bad or missing signature by %s\nChain is INVALID\n",
+					  uid);
+			g_free(uid);
+		
+			return FALSE;
+		}
+
+		uid = purple_certificate_get_unique_id(issuer);
+		purple_debug_info("certificate",
+				  "...Good signature by %s\n",
+				  uid);
+		g_free(uid);
+		
+		/* The issuer is now the next crt whose signature is to be
+		   checked */
+		crt = issuer;
+	}
+
+	/* If control reaches this point, the chain is valid */
+	purple_debug_info("certificate", "Chain is VALID\n");
+	return TRUE;
+}
+
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
+{
+	g_return_val_if_fail(scheme, NULL);
+	g_return_val_if_fail(scheme->import_certificate, NULL);
+	g_return_val_if_fail(filename, NULL);
+
+	return (scheme->import_certificate)(filename);
+}
+
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+
+	scheme = crt->scheme;
+	g_return_val_if_fail(scheme->export_certificate, FALSE);
+
+	return (scheme->export_certificate)(filename, crt);
+}
+
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	GByteArray *fpr;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
+
+	fpr = (scheme->get_fingerprint_sha1)(crt);
+
+	return fpr;
+}
+
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
+
+	return (crt->scheme->get_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
+{
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+	g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
+
+	return (crt->scheme->get_issuer_unique_id)(crt);
+}
+
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt)
+{
+	PurpleCertificateScheme *scheme;
+	gchar *subject_name;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme, NULL);
+
+	scheme = crt->scheme;
+
+	g_return_val_if_fail(scheme->get_subject_name, NULL);
+
+	subject_name = (scheme->get_subject_name)(crt);
+
+	return subject_name;
+}
+
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	scheme = crt->scheme;
+
+	/* TODO: Instead of failing, maybe use get_subject_name and strcmp? */
+	g_return_val_if_fail(scheme->check_subject_name, FALSE);
+
+	return (scheme->check_subject_name)(crt, name);
+}
+
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	PurpleCertificateScheme *scheme;
+
+	g_return_val_if_fail(crt, FALSE);
+
+	scheme = crt->scheme;
+	
+	g_return_val_if_fail(scheme, FALSE);
+
+	/* If both provided references are NULL, what are you doing calling
+	   this? */
+	g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
+
+	/* Throw the request on down to the certscheme */
+	return (scheme->get_times)(crt, activation, expiration);
+}
+
+
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
+{
+	gchar *path;
+	gchar *esc_scheme_name, *esc_name, *esc_id;
+	
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+	g_return_val_if_fail(pool->name, NULL);
+
+	/* Escape all the elements for filesystem-friendliness */
+	esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL;
+	esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL;
+	esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL;
+	
+	path = g_build_filename(purple_user_dir(),
+				"certificates", /* TODO: constantize this? */
+				esc_scheme_name,
+				esc_name,
+				esc_id,
+				NULL);
+
+	g_free(esc_scheme_name);
+	g_free(esc_name);
+	g_free(esc_id);
+	return path;
+}
+
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+
+	/* Check that the pool's scheme is loaded */
+	if (purple_certificate_find_scheme(pool->scheme_name) == NULL) {
+		return FALSE;
+	}
+	
+	return TRUE;
+}
+
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->scheme_name, NULL);
+
+	return purple_certificate_find_scheme(pool->scheme_name);
+}
+
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->cert_in_pool, FALSE);
+
+	return (pool->cert_in_pool)(id);
+}
+
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(id, NULL);
+	g_return_val_if_fail(pool->get_cert, NULL);
+
+	return (pool->get_cert)(id);
+}
+
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->put_cert, FALSE);
+
+	/* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
+	   relevant... I think... */
+	g_return_val_if_fail(
+		g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
+		FALSE);
+
+	ret = (pool->put_cert)(id, crt);
+
+	/* Signal that the certificate was stored if success*/
+	if (ret) {
+		purple_signal_emit(pool, "certificate-stored",
+				   pool, id);
+	}
+
+	return ret;
+}	
+
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(id, FALSE);
+	g_return_val_if_fail(pool->delete_cert, FALSE);
+
+	ret = (pool->delete_cert)(id);
+
+	/* Signal that the certificate was deleted if success */
+	if (ret) {
+		purple_signal_emit(pool, "certificate-deleted",
+				   pool, id);
+	}
+
+	return ret;
+}
+
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
+{
+	g_return_val_if_fail(pool, NULL);
+	g_return_val_if_fail(pool->get_idlist, NULL);
+
+	return (pool->get_idlist)();
+}
+
+void
+purple_certificate_pool_destroy_idlist(GList *idlist)
+{
+	GList *l;
+	
+	/* Iterate through and free them strings */
+	for ( l = idlist; l; l = l->next ) {
+		g_free(l->data);
+	}
+
+	g_list_free(idlist);
+}
+
+
+/****************************************************************************/
+/* Builtin Verifiers, Pools, etc.                                           */
+/****************************************************************************/
+
+static void
+x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
+{
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509_singleuse",
+			  "VRQ on cert from %s gave %d\n",
+			  vrq->subject_name, id);
+
+	/* Signal what happened back to the caller */
+	if (1 == id) {		
+		/* Accepted! */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		/* Not accepted */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+
+	}
+}
+
+static void
+x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	const gchar *cn_match;
+	gchar *primary, *secondary;
+	PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Determine whether the name matches */
+	if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
+		cn_match = _("");
+	} else {
+		cn_match = _("(DOES NOT MATCH)");
+	}
+	
+	/* Make messages */
+	primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
+	secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
+	
+	/* Make a semi-pretty display */
+	purple_request_accept_cancel(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("Single-use Certificate Verification"),
+		primary,
+		secondary,
+		1,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		vrq,
+		x509_singleuse_verify_cb,
+		x509_singleuse_verify_cb );
+	
+	/* Cleanup */
+	g_free(primary);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
+static void
+x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
+{
+	/* I don't do anything! */
+}
+
+PurpleCertificateVerifier x509_singleuse = {
+	"x509",                         /* Scheme name */
+	"singleuse",                    /* Verifier name */
+	x509_singleuse_start_verify,    /* start_verification function */
+	x509_singleuse_destroy_request, /* Request cleanup operation */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+
+
+/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
+/* This is implemented in what may be the most inefficient and bugprone way
+   possible; however, future optimizations should not be difficult. */
+
+static PurpleCertificatePool x509_ca;
+
+/** Holds a key-value pair for quickish certificate lookup */
+typedef struct {
+	gchar *dn;
+	PurpleCertificate *crt;
+} x509_ca_element;
+
+static void
+x509_ca_element_free(x509_ca_element *el)
+{
+	if (NULL == el) return;
+
+	g_free(el->dn);
+	purple_certificate_destroy(el->crt);
+	g_free(el);
+}
+
+/** System directory to probe for CA certificates */
+/* This is set in the lazy_init function */
+static const gchar *x509_ca_syspath = NULL;
+
+/** A list of loaded CAs, populated from the above path whenever the lazy_init
+    happens. Contains pointers to x509_ca_elements */
+static GList *x509_ca_certs = NULL;
+
+/** Used for lazy initialization purposes. */
+static gboolean x509_ca_initialized = FALSE;
+
+/** Adds a certificate to the in-memory cache, doing nothing else */
+static gboolean
+x509_ca_quiet_put_cert(PurpleCertificate *crt)
+{
+	x509_ca_element *el;
+
+	/* lazy_init calls this function, so calling lazy_init here is a
+	   Bad Thing */
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
+	
+	el = g_new0(x509_ca_element, 1);
+	el->dn = purple_certificate_get_unique_id(crt);
+	el->crt = purple_certificate_copy(crt);
+	x509_ca_certs = g_list_prepend(x509_ca_certs, el);
+
+	return TRUE;
+}
+
+/* Since the libpurple CertificatePools get registered before plugins are
+   loaded, an X.509 Scheme is generally not available when x509_ca_init is
+   called, but x509_ca requires X.509 operations in order to properly load.
+
+   To solve this, I present the lazy_init function. It attempts to finish
+   initialization of the Pool, but it usually fails when it is called from
+   x509_ca_init. However, this is OK; initialization is then simply deferred
+   until someone tries to use functions from the pool. */
+static gboolean
+x509_ca_lazy_init(void)
+{
+	PurpleCertificateScheme *x509;
+	GDir *certdir;
+	const gchar *entry;
+	GPatternSpec *pempat;
+	
+	if (x509_ca_initialized) return TRUE;
+
+	/* Check that X.509 is registered */
+	x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
+	if ( !x509 ) {
+		purple_debug_info("certificate/x509/ca",
+				  "Lazy init failed because an X.509 Scheme "
+				  "is not yet registered. Maybe it will be "
+				  "better later.\n");
+		return FALSE;
+	}
+
+	/* Attempt to point at the appropriate system path */
+	if (NULL == x509_ca_syspath) {
+#ifdef _WIN32
+		x509_ca_syspath = g_build_filename(DATADIR,
+						   "ca-certs", NULL);
+#else
+		x509_ca_syspath = g_build_filename(DATADIR,
+						   "purple", "ca-certs", NULL);
+#endif
+	}
+
+	/* Populate the certificates pool from the system path */
+	certdir = g_dir_open(x509_ca_syspath, 0, NULL);
+	g_return_val_if_fail(certdir, FALSE);
+
+	/* Use a glob to only read .pem files */
+	pempat = g_pattern_spec_new("*.pem");
+	
+	while ( (entry = g_dir_read_name(certdir)) ) {
+		gchar *fullpath;
+		PurpleCertificate *crt;
+
+		if ( !g_pattern_match_string(pempat, entry) ) {
+			continue;
+		}
+
+		fullpath = g_build_filename(x509_ca_syspath, entry, NULL);
+		
+		/* TODO: Respond to a failure in the following? */
+		crt = purple_certificate_import(x509, fullpath);
+
+		if (x509_ca_quiet_put_cert(crt)) {
+			purple_debug_info("certificate/x509/ca",
+					  "Loaded %s\n",
+					  fullpath);
+		} else {
+			purple_debug_error("certificate/x509/ca",
+					  "Failed to load %s\n",
+					  fullpath);
+		}
+
+		purple_certificate_destroy(crt);
+		g_free(fullpath);
+	}
+
+	g_pattern_spec_free(pempat);
+	g_dir_close(certdir);
+	
+	purple_debug_info("certificate/x509/ca",
+			  "Lazy init completed.\n");
+	x509_ca_initialized = TRUE;
+	return TRUE;
+}
+
+static gboolean
+x509_ca_init(void)
+{
+	/* Attempt to initialize now, but if it doesn't work, that's OK;
+	   it will get done later */
+	if ( ! x509_ca_lazy_init()) {
+		purple_debug_info("certificate/x509/ca",
+				  "Init failed, probably because a "
+				  "dependency is not yet registered. "
+				  "It has been deferred to later.\n");
+	}
+	
+	return TRUE;
+}
+
+static void
+x509_ca_uninit(void)
+{
+	GList *l;
+
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		x509_ca_element_free(el);
+	}
+	g_list_free(x509_ca_certs);
+	x509_ca_certs = NULL;
+	x509_ca_initialized = FALSE;
+}
+
+/** Look up a ca_element by dn */
+static x509_ca_element *
+x509_ca_locate_cert(GList *lst, const gchar *dn)
+{
+	GList *cur;
+
+	for (cur = lst; cur; cur = cur->next) {
+		x509_ca_element *el = cur->data;
+		/* TODO: Unsafe? */
+		if ( !strcmp(dn, el->dn) ) {
+			return el;
+		}
+	}
+	return NULL;
+}
+
+static gboolean
+x509_ca_cert_in_pool(const gchar *id)
+{
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_ca_get_cert(const gchar *id)
+{
+	PurpleCertificate *crt = NULL;
+	x509_ca_element *el;
+
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+	g_return_val_if_fail(id, NULL);
+
+	/* Search the memory-cached pool */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+
+	if (el != NULL) {
+		/* Make a copy of the memcached one for the function caller
+		   to play with */
+		crt = purple_certificate_copy(el->crt);
+	} else {
+		crt = NULL;
+	}
+	
+	return crt;
+}
+
+static gboolean
+x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+
+	/* TODO: This is a quick way of doing this. At some point the change
+	   ought to be flushed to disk somehow. */
+	ret = x509_ca_quiet_put_cert(crt);
+
+	return ret;
+}
+
+static gboolean
+x509_ca_delete_cert(const gchar *id)
+{
+	x509_ca_element *el;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	el = x509_ca_locate_cert(x509_ca_certs, id);
+	if ( el == NULL ) {
+		purple_debug_warning("certificate/x509/ca",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* Unlink it from the memory cache and destroy it */
+	x509_ca_certs = g_list_remove(x509_ca_certs, el);
+	x509_ca_element_free(el);
+	
+	return TRUE;
+}
+
+static GList *
+x509_ca_get_idlist(void)
+{
+	GList *l, *idlist;
+	
+	g_return_val_if_fail(x509_ca_lazy_init(), NULL);
+
+	idlist = NULL;
+	for (l = x509_ca_certs; l; l = l->next) {
+		x509_ca_element *el = l->data;
+		idlist = g_list_prepend(idlist, g_strdup(el->dn));
+	}
+	
+	return idlist;
+}
+
+
+static PurpleCertificatePool x509_ca = {
+	"x509",                       /* Scheme name */
+	"ca",                         /* Pool name */
+	N_("Certificate Authorities"),/* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_ca_init,                 /* init */
+	x509_ca_uninit,               /* uninit */
+	x509_ca_cert_in_pool,         /* Certificate exists? */
+	x509_ca_get_cert,             /* Cert retriever */
+	x509_ca_put_cert,             /* Cert writer */
+	x509_ca_delete_cert,          /* Cert remover */
+	x509_ca_get_idlist,           /* idlist retriever */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+
+};
+
+
+
+/***** Cache of certificates given by TLS/SSL peers *****/
+static PurpleCertificatePool x509_tls_peers;
+
+static gboolean
+x509_tls_peers_init(void)
+{
+	gchar *poolpath;
+	int ret;
+	
+	/* Set up key cache here if it isn't already done */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
+
+	g_free(poolpath);
+
+	g_return_val_if_fail(ret == 0, FALSE);
+	return TRUE;
+}
+
+static gboolean
+x509_tls_peers_cert_in_pool(const gchar *id)
+{
+	gchar *keypath;
+	gboolean ret = FALSE;
+	
+	g_return_val_if_fail(id, FALSE);
+
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+
+	ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static PurpleCertificate *
+x509_tls_peers_get_cert(const gchar *id)
+{
+	PurpleCertificateScheme *x509;
+	PurpleCertificate *crt;
+	gchar *keypath;
+	
+	g_return_val_if_fail(id, NULL);
+
+	/* Is it in the pool? */
+	if ( !x509_tls_peers_cert_in_pool(id) ) {
+		return NULL;
+	}
+	
+	/* Look up the X.509 scheme */
+	x509 = purple_certificate_find_scheme("x509");
+	g_return_val_if_fail(x509, NULL);
+
+	/* Okay, now find and load that key */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	crt = purple_certificate_import(x509, keypath);
+
+	g_free(keypath);
+
+	return crt;
+}
+
+static gboolean
+x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme, FALSE);
+	/* Make sure that this is some kind of X.509 certificate */
+	/* TODO: Perhaps just check crt->scheme->name instead? */
+	g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE);
+
+	/* Work out the filename and export */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
+	ret = purple_certificate_export(keypath, crt);
+	
+	g_free(keypath);
+	return ret;
+}
+
+static gboolean
+x509_tls_peers_delete_cert(const gchar *id)
+{
+	gboolean ret = FALSE;
+	gchar *keypath;
+
+	g_return_val_if_fail(id, FALSE);
+
+	/* Is the id even in the pool? */
+	if (!x509_tls_peers_cert_in_pool(id)) {
+		purple_debug_warning("certificate/tls_peers",
+				     "Id %s wasn't in the pool\n",
+				     id);
+		return FALSE;
+	}
+
+	/* OK, so work out the keypath and delete the thing */
+	keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);	
+	if ( unlink(keypath) != 0 ) {
+		purple_debug_error("certificate/tls_peers",
+				   "Unlink of %s failed!\n",
+				   keypath);
+		ret = FALSE;
+	} else {
+		ret = TRUE;
+	}
+
+	g_free(keypath);
+	return ret;
+}
+
+static GList *
+x509_tls_peers_get_idlist(void)
+{
+	GList *idlist = NULL;
+	GDir *dir;
+	const gchar *entry;
+	gchar *poolpath;
+
+	/* Get a handle on the pool directory */
+	poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
+	dir = g_dir_open(poolpath,
+			 0,     /* No flags */
+			 NULL); /* Not interested in what the error is */
+	g_free(poolpath);
+
+	g_return_val_if_fail(dir, NULL);
+
+	/* Traverse the directory listing and create an idlist */
+	while ( (entry = g_dir_read_name(dir)) != NULL ) {
+		/* Unescape the filename */
+		const char *unescaped = purple_unescape_filename(entry);
+		
+		/* Copy the entry name into our list (GLib owns the original
+		   string) */
+		idlist = g_list_prepend(idlist, g_strdup(unescaped));
+	}
+
+	/* Release the directory */
+	g_dir_close(dir);
+	
+	return idlist;
+}
+
+static PurpleCertificatePool x509_tls_peers = {
+	"x509",                       /* Scheme name */
+	"tls_peers",                  /* Pool name */
+	N_("SSL Peers Cache"),        /* User-friendly name */
+	NULL,                         /* Internal data */
+	x509_tls_peers_init,          /* init */
+	NULL,                         /* uninit not required */
+	x509_tls_peers_cert_in_pool,  /* Certificate exists? */
+	x509_tls_peers_get_cert,      /* Cert retriever */
+	x509_tls_peers_put_cert,      /* Cert writer */
+	x509_tls_peers_delete_cert,   /* Cert remover */
+	x509_tls_peers_get_idlist,    /* idlist retriever */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+
+/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
+static PurpleCertificateVerifier x509_tls_cached;
+
+
+/* The following is several hacks piled together and needs to be fixed.
+ * It exists because show_cert (see its comments) needs the original reason
+ * given to user_auth in order to rebuild the dialog.
+ */
+/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
+   closed by handle or otherwise abnormally. */
+typedef struct {
+	PurpleCertificateVerificationRequest *vrq;
+	gchar *reason;
+} x509_tls_cached_ua_ctx;
+
+static x509_tls_cached_ua_ctx *
+x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
+			   const gchar *reason)
+{
+	x509_tls_cached_ua_ctx *c;
+
+	c = g_new0(x509_tls_cached_ua_ctx, 1);
+	c->vrq = vrq;
+	c->reason = g_strdup(reason);
+
+	return c;
+}
+
+
+static void
+x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
+{
+	g_return_if_fail(c);
+	g_free(c->reason);
+	g_free(c);
+}
+
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason);
+
+static void
+x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
+
+	/* Since clicking a button closes the request, show it again */
+	x509_tls_cached_user_auth(c->vrq, c->reason);
+
+	/* Show the certificate AFTER re-opening the dialog so that this
+	   appears above the other */
+	purple_certificate_display_x509(disp_crt);
+
+	x509_tls_cached_ua_ctx_free(c);
+}
+
+static void
+x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
+{
+	PurpleCertificateVerificationRequest *vrq;
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(c);
+	g_return_if_fail(c->vrq);
+	
+	vrq = c->vrq;
+
+	x509_tls_cached_ua_ctx_free(c);
+
+	tls_peers = purple_certificate_find_pool("x509","tls_peers");
+
+	if (2 == id) {
+		gchar *cache_id = vrq->subject_name;
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
+				  cache_id);
+		
+		purple_certificate_pool_store(tls_peers, cache_id,
+					      vrq->cert_chain->data);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "User REJECTED cert\n");
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	}
+}
+
+static void
+x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore)
+{
+	x509_tls_cached_user_auth_cb(c, 2);
+}
+
+static void
+x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore)
+{
+	x509_tls_cached_user_auth_cb(c, 1);
+}
+
+/** Validates a certificate by asking the user
+ * @param reason    String to explain why the user needs to accept/refuse the
+ *                  certificate.
+ * @todo Needs a handle argument
+ */
+static void
+x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
+			  const gchar *reason)
+{
+	gchar *primary;
+
+	/* Make messages */
+	primary = g_strdup_printf(_("Accept certificate for %s?"),
+				  vrq->subject_name);
+		
+	/* Make a semi-pretty display */
+	purple_request_action(
+		vrq->cb_data, /* TODO: Find what the handle ought to be */
+		_("SSL Certificate Verification"),
+		primary,
+		reason,
+		2,            /* Accept by default */
+		NULL,         /* No account */
+		NULL,         /* No other user */
+		NULL,         /* No associated conversation */
+		x509_tls_cached_ua_ctx_new(vrq, reason),
+		3,            /* Number of actions */
+		_("Accept"), x509_tls_cached_user_auth_accept_cb,
+		_("Reject"),  x509_tls_cached_user_auth_reject_cb,
+		_("_View Certificate..."), x509_tls_cached_show_cert);
+	
+	/* Cleanup */
+	g_free(primary);
+}
+
+static void
+x509_tls_cached_peer_cert_changed(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Prompt the user, etc. */
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Certificate for %s does not match cached. "
+			  "Auto-rejecting!\n",
+			  vrq->subject_name);
+	
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
+	return;
+}
+
+static void
+x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq)
+{
+	/* TODO: Looking this up by name over and over is expensive.
+	   Fix, please! */
+	PurpleCertificatePool *tls_peers =
+		purple_certificate_find_pool(x509_tls_cached.scheme_name,
+					     "tls_peers");
+
+	/* The peer's certificate should be the first in the list */
+	PurpleCertificate *peer_crt =
+		(PurpleCertificate *) vrq->cert_chain->data;
+	
+	PurpleCertificate *cached_crt;
+	GByteArray *peer_fpr, *cached_fpr;
+
+	/* Load up the cached certificate */
+	cached_crt = purple_certificate_pool_retrieve(
+		tls_peers, vrq->subject_name);
+	if ( !cached_crt ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Lookup failed on cached certificate!\n"
+				   "It was here just a second ago. Forwarding "
+				   "to cert_changed.\n");
+		/* vrq now becomes the problem of cert_changed */
+		x509_tls_cached_peer_cert_changed(vrq);
+	}
+
+	/* Now get SHA1 sums for both and compare them */
+	/* TODO: This is not an elegant way to compare certs */
+	peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
+	cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
+	if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert matched cached\n");
+		/* vrq is now finished */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_VALID);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Peer cert did NOT match cached\n");
+		/* vrq now becomes the problem of cert_changed */
+		x509_tls_cached_peer_cert_changed(vrq);
+	}
+	
+	purple_certificate_destroy(cached_crt);
+	g_byte_array_free(peer_fpr, TRUE);
+	g_byte_array_free(cached_fpr, TRUE);
+}
+
+/* For when we've never communicated with this party before */
+/* TODO: Need ways to specify possibly multiple problems with a cert, or at
+   least  reprioritize them. For example, maybe the signature ought to be
+   checked BEFORE the hostname checking? */
+static void
+x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq)
+{
+	PurpleCertificatePool *ca, *tls_peers;
+	PurpleCertificate *end_crt, *ca_crt, *peer_crt;
+	GList *chain = vrq->cert_chain;
+	GList *last;
+	gchar *ca_id;
+
+	peer_crt = (PurpleCertificate *) chain->data;
+
+	/* First, check that the hostname matches */
+	if ( ! purple_certificate_check_subject_name(peer_crt,
+						     vrq->subject_name) ) {
+		gchar *sn = purple_certificate_get_subject_name(peer_crt);
+		gchar *msg;
+		
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Name mismatch: Certificate given for %s "
+				  "has a name of %s\n",
+				  vrq->subject_name, sn);
+
+		/* Prompt the user to authenticate the certificate */
+		/* TODO: Provide the user with more guidance about why he is
+		   being prompted */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"claims to be from \"%s\" instead.  "
+					"This could mean that you are not "
+					"connecting to the service you "
+					"believe you are."),
+				      vrq->subject_name, sn);
+				      
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(sn);
+		g_free(msg);
+		return;
+	} /* if (name mismatch) */
+
+	/* TODO: Figure out a way to check for a bad signature, as opposed to
+	   "not self-signed" */
+	if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
+		gchar *msg;
+		
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Certificate for %s is self-signed.\n",
+				  vrq->subject_name);
+
+		/* Prompt the user to authenticate the certificate */
+		/* vrq will be completed by user_auth */
+		msg = g_strdup_printf(_("The certificate presented by \"%s\" "
+					"is self-signed. It cannot be "
+					"automatically checked."),
+				      vrq->subject_name);
+				      
+		x509_tls_cached_user_auth(vrq,msg);
+
+		g_free(msg);
+		return;
+	} /* if (name mismatch) */
+	
+	/* Next, check that the certificate chain is valid */
+	if ( ! purple_certificate_check_signature_chain(chain) ) {
+		/* TODO: Tell the user where the chain broke? */
+		/* TODO: This error will hopelessly confuse any
+		   non-elite user. */
+		gchar *secondary;
+
+		secondary = g_strdup_printf(_("The certificate chain presented"
+					      " for %s is not valid."),
+					    vrq->subject_name);
+
+		/* TODO: Make this error either block the ensuing SSL
+		   connection error until the user dismisses this one, or
+		   stifle it. */
+		purple_notify_error(NULL, /* TODO: Probably wrong. */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate chain"),
+				    secondary );
+		g_free(secondary);
+
+		/* Okay, we're done here */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+	} /* if (signature chain not good) */
+
+	/* Next, attempt to verify the last certificate against a CA */
+	ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
+
+	/* If, for whatever reason, there is no Certificate Authority pool
+	   loaded, we will simply present it to the user for checking. */
+	if ( !ca ) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "No X.509 Certificate Authority pool "
+				   "could be found!\n");
+
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("You have no database of root "
+						"certificates, so this "
+						"certificate cannot be "
+						"validated."));
+		return;
+	}
+
+	last = g_list_last(chain);
+	end_crt = (PurpleCertificate *) last->data;
+
+	/* Attempt to look up the last certificate's issuer */
+	ca_id = purple_certificate_get_issuer_unique_id(end_crt);
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for a CA with DN=%s\n",
+			  ca_id);
+	if ( !purple_certificate_pool_contains(ca, ca_id) ) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "Certificate Authority with DN='%s' not "
+				  "found. I'll prompt the user, I guess.\n",
+				  ca_id);
+		g_free(ca_id);
+		/* vrq will be completed by user_auth */
+		x509_tls_cached_user_auth(vrq,_("The root certificate this "
+						"one claims to be issued by "
+						"is unknown to Pidgin."));
+		return;
+	}
+
+	ca_crt = purple_certificate_pool_retrieve(ca, ca_id);
+	g_free(ca_id);
+	if (!ca_crt) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Certificate authority disappeared out "
+				   "underneath me!\n");
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	}
+	
+	/* Check the signature */
+	if ( !purple_certificate_signed_by(end_crt, ca_crt) ) {
+		/* TODO: If signed_by ever returns a reason, maybe mention
+		   that, too. */
+		/* TODO: Also mention the CA involved. While I could do this
+		   now, a full DN is a little much with which to assault the
+		   user's poor, leaky eyes. */
+		/* TODO: This error message makes my eyes cross, and I wrote it */
+		gchar * secondary =
+			g_strdup_printf(_("The certificate chain presented by "
+					  "%s does not have a valid digital "
+					  "signature from the Certificate "
+					  "Authority from which it claims to "
+					  "have a signature."),
+					vrq->subject_name);
+		
+		purple_notify_error(NULL, /* TODO: Probably wrong */
+				    _("SSL Certificate Error"),
+				    _("Invalid certificate authority"
+				      " signature"),
+				    secondary);
+		g_free(secondary);
+
+		/* Signal "bad cert" */
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	} /* if (CA signature not good) */
+
+	/* If we reach this point, the certificate is good. */
+	/* Look up the local cache and store it there for future use */
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
+						 "tls_peers");
+
+	if (tls_peers) {
+		if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
+						   peer_crt) ) {
+			purple_debug_error("certificate/x509/tls_cached",
+					   "FAILED to cache peer certificate\n");
+		}
+	} else {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Unable to locate tls_peers certificate "
+				   "cache.\n");
+	}
+	
+	/* Whew! Done! */
+	purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
+}
+
+static void
+x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
+{
+	const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
+	PurpleCertificatePool *tls_peers;
+
+	g_return_if_fail(vrq);
+
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Starting verify for %s\n",
+			  vrq->subject_name);
+	
+	tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
+
+	/* TODO: This should probably just prompt the user instead of throwing
+	   an angry fit */
+	if (!tls_peers) {
+		purple_debug_error("certificate/x509/tls_cached",
+				   "Couldn't find local peers cache %s\nReturning INVALID to callback\n",
+				   tls_peers_name);
+
+		purple_certificate_verify_complete(vrq,
+						   PURPLE_CERTIFICATE_INVALID);
+		return;
+	}
+	
+	/* Check if the peer has a certificate cached already */
+	purple_debug_info("certificate/x509/tls_cached",
+			  "Checking for cached cert...\n");
+	if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Found cached cert\n");
+		/* vrq is now the responsibility of cert_in_cache */
+		x509_tls_cached_cert_in_cache(vrq);
+	} else {
+		purple_debug_info("certificate/x509/tls_cached",
+				  "...Not in cache\n");
+		/* vrq now becomes the problem of unknown_peer */
+		x509_tls_cached_unknown_peer(vrq);
+	}
+}
+
+static void
+x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
+{
+	g_return_if_fail(vrq);
+}
+
+static PurpleCertificateVerifier x509_tls_cached = {
+	"x509",                         /* Scheme name */
+	"tls_cached",                   /* Verifier name */
+	x509_tls_cached_start_verify,   /* Verification begin */
+	x509_tls_cached_destroy_request,/* Request cleanup */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+
+};
+
+/****************************************************************************/
+/* Subsystem                                                                */
+/****************************************************************************/
+void
+purple_certificate_init(void)
+{
+	/* Register builtins */
+	purple_certificate_register_verifier(&x509_singleuse);
+	purple_certificate_register_pool(&x509_ca);
+	purple_certificate_register_pool(&x509_tls_peers);
+	purple_certificate_register_verifier(&x509_tls_cached);
+}
+
+void
+purple_certificate_uninit(void)
+{
+	GList *full_list, *l;
+
+	/* Unregister all Schemes */
+	full_list = g_list_copy(cert_schemes); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_scheme(
+			(PurpleCertificateScheme *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Verifiers */
+	full_list = g_list_copy(cert_verifiers); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_verifier(
+			(PurpleCertificateVerifier *) l->data );
+	}
+	g_list_free(full_list);
+
+	/* Unregister all Pools */
+	full_list = g_list_copy(cert_pools); /* Make a working copy */
+	for (l = full_list; l; l = l->next) {
+		purple_certificate_unregister_pool(
+			(PurpleCertificatePool *) l->data );
+	}
+	g_list_free(full_list);
+}
+
+gpointer
+purple_certificate_get_handle(void)
+{
+	static gint handle;
+	return &handle;
+}
+
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name)
+{
+	PurpleCertificateScheme *scheme = NULL;
+	GList *l;
+
+	g_return_val_if_fail(name, NULL);
+
+	/* Traverse the list of registered schemes and locate the
+	   one whose name matches */
+	for(l = cert_schemes; l; l = l->next) {
+		scheme = (PurpleCertificateScheme *)(l->data);
+
+		/* Name matches? that's our man */
+		if(!g_ascii_strcasecmp(scheme->name, name))
+			return scheme;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateScheme %s requested but not found.\n",
+			     name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+GList *
+purple_certificate_get_schemes(void)
+{
+	return cert_schemes;
+}
+
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
+{
+	g_return_val_if_fail(scheme != NULL, FALSE);
+
+	/* Make sure no scheme is registered with the same name */
+	if (purple_certificate_find_scheme(scheme->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_schemes = g_list_prepend(cert_schemes, scheme);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s registered\n",
+			  scheme->name);
+	
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
+{
+	if (NULL == scheme) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL scheme\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	/* TODO: unregister all CertificateVerifiers for this scheme?*/
+	/* TODO: unregister all CertificatePools for this scheme? */
+	/* Neither of the above should be necessary, though */
+	cert_schemes = g_list_remove(cert_schemes, scheme);
+
+	purple_debug_info("certificate",
+			  "CertificateScheme %s unregistered\n",
+			  scheme->name);
+
+
+	return TRUE;
+}
+
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
+{
+	PurpleCertificateVerifier *vr = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(ver_name, NULL);
+
+	/* Traverse the list of registered verifiers and locate the
+	   one whose name matches */
+	for(l = cert_verifiers; l; l = l->next) {
+		vr = (PurpleCertificateVerifier *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(vr->name, ver_name))
+			return vr;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificateVerifier %s, %s requested but not found.\n",
+			     scheme_name, ver_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+}
+
+
+GList *
+purple_certificate_get_verifiers(void)
+{
+	return cert_verifiers;
+}
+
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
+{
+	g_return_val_if_fail(vr != NULL, FALSE);
+
+	/* Make sure no verifier is registered with the same scheme/name */
+	if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
+		return FALSE;
+	}
+
+	/* Okay, we're golden. Register it. */
+	cert_verifiers = g_list_prepend(cert_verifiers, vr);
+
+	/* TODO: Signalling and such? */
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s registered\n",
+			  vr->name);
+	return TRUE;
+}
+
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
+{
+	if (NULL == vr) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL verifier\n");
+		return FALSE;
+	}
+
+	/* TODO: signalling? */
+
+	cert_verifiers = g_list_remove(cert_verifiers, vr);
+
+
+	purple_debug_info("certificate",
+			  "CertificateVerifier %s unregistered\n",
+			  vr->name);
+
+	return TRUE;
+}
+
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
+{
+	PurpleCertificatePool *pool = NULL;
+	GList *l;
+
+	g_return_val_if_fail(scheme_name, NULL);
+	g_return_val_if_fail(pool_name, NULL);
+
+	/* Traverse the list of registered pools and locate the
+	   one whose name matches */
+	for(l = cert_pools; l; l = l->next) {
+		pool = (PurpleCertificatePool *)(l->data);
+
+		/* Scheme and name match? */
+		if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) &&
+		   !g_ascii_strcasecmp(pool->name, pool_name))
+			return pool;
+	}
+
+	purple_debug_warning("certificate",
+			     "CertificatePool %s, %s requested but not found.\n",
+			     scheme_name, pool_name);
+
+	/* TODO: Signalling and such? */
+	
+	return NULL;
+
+}
+
+GList *
+purple_certificate_get_pools(void)
+{
+	return cert_pools;
+}
+
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool)
+{
+	gboolean success = FALSE;
+	g_return_val_if_fail(pool, FALSE);
+	g_return_val_if_fail(pool->scheme_name, FALSE);
+	g_return_val_if_fail(pool->name, FALSE);
+	g_return_val_if_fail(pool->fullname, FALSE);
+
+	/* Make sure no pools are registered under this name */
+	if (purple_certificate_find_pool(pool->scheme_name, pool->name)) {
+		return FALSE;
+	}
+
+	/* Initialize the pool if needed */
+	if (pool->init) {
+		success = pool->init();
+	} else {
+		success = TRUE;
+	}
+	
+	if (success) {
+		/* Register the Pool */
+		cert_pools = g_list_prepend(cert_pools, pool);
+
+		/* TODO: Emit a signal that the pool got registered */
+
+		PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool);
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-stored",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+		purple_signal_register(pool, /* Signals emitted from pool */
+				       "certificate-deleted",
+				       purple_marshal_VOID__POINTER_POINTER,
+				       NULL, /* No callback return value */
+				       2,    /* Two non-data arguments */
+				       purple_value_new(PURPLE_TYPE_SUBTYPE,
+							PURPLE_SUBTYPE_CERTIFICATEPOOL),
+				       purple_value_new(PURPLE_TYPE_STRING));
+
+
+		purple_debug_info("certificate",
+			  "CertificatePool %s registered\n",
+			  pool->name);
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+	
+	/* Control does not reach this point */
+}
+
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool)
+{
+	if (NULL == pool) {
+		purple_debug_warning("certificate",
+				     "Attempting to unregister NULL pool\n");
+		return FALSE;
+	}
+
+	/* Check that the pool is registered */
+	if (!g_list_find(cert_pools, pool)) {
+		purple_debug_warning("certificate",
+				     "Pool to unregister isn't registered!\n");
+
+		return FALSE;
+	}
+
+	/* Uninit the pool if needed */
+	PURPLE_DBUS_UNREGISTER_POINTER(pool);
+	if (pool->uninit) {
+		pool->uninit();
+	}
+
+	cert_pools = g_list_remove(cert_pools, pool);
+	
+	/* TODO: Signalling? */
+	purple_signal_unregister(pool, "certificate-stored");
+	purple_signal_unregister(pool, "certificate-deleted");
+		
+	purple_debug_info("certificate",
+			  "CertificatePool %s unregistered\n",
+			  pool->name);
+	return TRUE;
+}
+
+/****************************************************************************/
+/* Scheme-specific functions                                                */
+/****************************************************************************/
+
+void
+purple_certificate_display_x509(PurpleCertificate *crt)
+{
+	gchar *sha_asc;
+	GByteArray *sha_bin;
+	gchar *cn;
+	time_t activation, expiration;
+	gchar *activ_str, *expir_str;
+	gchar *secondary;
+
+	/* Pull out the SHA1 checksum */
+	sha_bin = purple_certificate_get_fingerprint_sha1(crt);
+	/* Now decode it for display */
+	sha_asc = purple_base16_encode_chunked(sha_bin->data,
+					       sha_bin->len);
+
+	/* Get the cert Common Name */
+	/* TODO: Will break on CA certs */
+	cn = purple_certificate_get_subject_name(crt);
+
+	/* Get the certificate times */
+	/* TODO: Check the times against localtime */
+	/* TODO: errorcheck? */
+	if (!purple_certificate_get_times(crt, &activation, &expiration)) {
+		purple_debug_error("certificate",
+				   "Failed to get certificate times!\n");
+		activation = expiration = 0;
+	}
+	activ_str = g_strdup(ctime(&activation));
+	expir_str = g_strdup(ctime(&expiration));
+
+	/* Make messages */
+	secondary = g_strdup_printf(_("Common name: %s\n\n"
+				      "Fingerprint (SHA1): %s\n\n"
+				      "Activation date: %s\n"
+				      "Expiration date: %s\n"),
+				    cn, sha_asc, activ_str, expir_str);
+
+	/* Make a semi-pretty display */
+	purple_notify_info(
+		NULL,         /* TODO: Find what the handle ought to be */
+		_("Certificate Information"),
+		"",
+		secondary);
+
+	/* Cleanup */
+	g_free(cn);
+	g_free(secondary);
+	g_free(sha_asc);
+	g_free(activ_str);
+	g_free(expir_str);
+	g_byte_array_free(sha_bin, TRUE);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/certificate.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,792 @@
+/**
+ * @file certificate.h Public-Key Certificate API
+ * @ingroup core
+ */
+
+/*
+ *
+ * purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * 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
+ */
+
+#ifndef _PURPLE_CERTIFICATE_H
+#define _PURPLE_CERTIFICATE_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+typedef enum
+{
+	PURPLE_CERTIFICATE_INVALID = 0,
+	PURPLE_CERTIFICATE_VALID = 1
+} PurpleCertificateVerificationStatus;
+
+typedef struct _PurpleCertificate PurpleCertificate;
+typedef struct _PurpleCertificatePool PurpleCertificatePool;
+typedef struct _PurpleCertificateScheme PurpleCertificateScheme;
+typedef struct _PurpleCertificateVerifier PurpleCertificateVerifier;
+typedef struct _PurpleCertificateVerificationRequest PurpleCertificateVerificationRequest;
+
+/**
+ * Callback function for the results of a verification check
+ * @param st       Status code
+ * @param userdata User-defined data
+ */
+typedef void (*PurpleCertificateVerifiedCallback)
+		(PurpleCertificateVerificationStatus st,
+		 gpointer userdata);
+							  
+/** A certificate instance
+ *
+ *  An opaque data structure representing a single certificate under some
+ *  CertificateScheme
+ */
+struct _PurpleCertificate
+{
+	/** Scheme this certificate is under */
+	PurpleCertificateScheme * scheme;
+	/** Opaque pointer to internal data */
+	gpointer data;
+};
+
+/**
+ * Database for retrieval or storage of Certificates
+ *
+ * More or less a hash table; all lookups and writes are controlled by a string
+ * key.
+ */
+struct _PurpleCertificatePool
+{
+	/** Scheme this Pool operates for */
+	gchar *scheme_name;
+	/** Internal name to refer to the pool by */
+	gchar *name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("SSL Servers")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(pool->fullname)
+	 */
+	gchar *fullname;
+
+	/** Internal pool data */
+	gpointer data;
+	
+	/**
+	 * Set up the Pool's internal state
+	 *
+	 * Upon calling purple_certificate_register_pool() , this function will
+	 * be called. May be NULL.
+	 * @return TRUE if the initialization succeeded, otherwise FALSE
+	 */
+	gboolean (* init)(void);
+
+	/**
+	 * Uninit the Pool's internal state
+	 *
+	 * Will be called by purple_certificate_unregister_pool() . May be NULL
+	 */
+	void (* uninit)(void);
+
+	/** Check for presence of a certificate in the pool using unique ID */
+	gboolean (* cert_in_pool)(const gchar *id);
+	/** Retrieve a PurpleCertificate from the pool */
+	PurpleCertificate * (* get_cert)(const gchar *id);
+	/** Add a certificate to the pool. Must overwrite any other
+	 *  certificates sharing the same ID in the pool.
+	 *  @return TRUE if the operation succeeded, otherwise FALSE
+	 */
+	gboolean (* put_cert)(const gchar *id, PurpleCertificate *crt);
+	/** Delete a certificate from the pool */
+	gboolean (* delete_cert)(const gchar *id);
+
+	/** Returns a list of IDs stored in the pool */
+	GList * (* get_idlist)(void);
+
+	void (*_purple_reserved1)(void);
+	void (*_purple_reserved2)(void);
+	void (*_purple_reserved3)(void);
+	void (*_purple_reserved4)(void);
+};
+
+/** A certificate type
+ *
+ *  A CertificateScheme must implement all of the fields in the structure,
+ *  and register it using purple_certificate_register_scheme()
+ *
+ *  There may be only ONE CertificateScheme provided for each certificate
+ *  type, as specified by the "name" field.
+ */
+struct _PurpleCertificateScheme
+{
+	/** Name of the certificate type
+	 *  ex: "x509", "pgp", etc.
+	 *  This must be globally unique - you may not register more than one
+	 *  CertificateScheme of the same name at a time.
+	 */
+	gchar * name;
+
+	/** User-friendly name for this type
+	 *  ex: N_("X.509 Certificates")
+	 *  When this is displayed anywhere, it should be i18ned
+	 *  ex: _(scheme->fullname)
+	 */
+	gchar * fullname;
+
+	/** Imports a certificate from a file
+	 *
+	 *  @param filename   File to import the certificate from
+	 *  @return           Pointer to the newly allocated Certificate struct
+	 *                    or NULL on failure.
+	 */
+	PurpleCertificate * (* import_certificate)(const gchar * filename);
+
+	/**
+	 * Exports a certificate to a file
+	 *
+	 * @param filename    File to export the certificate to
+	 * @param crt         Certificate to export
+	 * @return TRUE if the export succeeded, otherwise FALSE
+	 * @see purple_certificate_export()
+	 */
+	gboolean (* export_certificate)(const gchar *filename, PurpleCertificate *crt);
+
+	/**
+	 * Duplicates a certificate
+	 *
+	 * Certificates are generally assumed to be read-only, so feel free to
+	 * do any sort of reference-counting magic you want here. If this ever
+	 * changes, please remember to change the magic accordingly.
+	 * @return Reference to the new copy
+	 */
+	PurpleCertificate * (* copy_certificate)(PurpleCertificate *crt);
+
+	/** Destroys and frees a Certificate structure
+	 *
+	 *  Destroys a Certificate's internal data structures and calls
+	 *  free(crt)
+	 *
+	 *  @param crt  Certificate instance to be destroyed. It WILL NOT be
+	 *              destroyed if it is not of the correct
+	 *              CertificateScheme. Can be NULL
+	 */
+	void (* destroy_certificate)(PurpleCertificate * crt);
+
+	/** Find whether "crt" has a valid signature from issuer "issuer"
+	 *  @see purple_certificate_signed_by() */
+	gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer);
+	/**
+	 * Retrieves the certificate public key fingerprint using SHA1
+	 *
+	 * @param crt   Certificate instance
+	 * @return Binary representation of SHA1 hash - must be freed using
+	 *         g_byte_array_free()
+	 */
+	GByteArray * (* get_fingerprint_sha1)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique certificate identifier
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the certificate.
+	 */
+	gchar * (* get_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Retrieves a unique identifier for the certificate's issuer
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string that can be used to uniquely
+	 *         identify the issuer's certificate.
+	 */
+	gchar * (* get_issuer_unique_id)(PurpleCertificate *crt);
+
+	/**
+	 * Gets the certificate subject's name
+	 *
+	 * For X.509, this is the "Common Name" field, as we're only using it
+	 * for hostname verification at the moment
+	 *
+	 * @see purple_certificate_get_subject_name()
+	 *
+	 * @param crt   Certificate instance
+	 * @return Newly allocated string with the certificate subject.
+	 */
+	gchar * (* get_subject_name)(PurpleCertificate *crt);
+
+	/**
+	 * Check the subject name against that on the certificate
+	 * @see purple_certificate_check_subject_name()
+	 * @return TRUE if it is a match, else FALSE
+	 */
+	gboolean (* check_subject_name)(PurpleCertificate *crt, const gchar *name);
+
+	/** Retrieve the certificate activation/expiration times */
+	gboolean (* get_times)(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+	
+	void (*_purple_reserved1)(void);
+	void (*_purple_reserved2)(void);
+	void (*_purple_reserved3)(void);
+	void (*_purple_reserved4)(void);
+};
+
+/** A set of operations used to provide logic for verifying a Certificate's
+ *  authenticity.
+ *
+ * A Verifier provider must fill out these fields, then register it using
+ * purple_certificate_register_verifier()
+ *
+ * The (scheme_name, name) value must be unique for each Verifier - you may not
+ * register more than one Verifier of the same name for each Scheme
+ */
+struct _PurpleCertificateVerifier
+{
+	/** Name of the scheme this Verifier operates on
+	 *
+	 * The scheme will be looked up by name when a Request is generated
+	 * using this Verifier
+	 */
+	gchar *scheme_name;
+
+	/** Name of the Verifier - case insensitive */
+	gchar *name;
+	
+	/**
+	 * Start the verification process
+	 *
+	 * To be called from purple_certificate_verify once it has
+	 * constructed the request. This will use the information in the
+	 * given VerificationRequest to check the certificate and callback
+	 * the requester with the verification results.
+	 *
+	 * @param vrq      Request to process
+	 */
+	void (* start_verification)(PurpleCertificateVerificationRequest *vrq);
+
+	/**
+	 * Destroy a completed Request under this Verifier
+	 * The function pointed to here is only responsible for cleaning up
+	 * whatever PurpleCertificateVerificationRequest::data points to.
+	 * It should not call free(vrq)
+	 *
+	 * @param vrq       Request to destroy
+	 */
+	void (* destroy_request)(PurpleCertificateVerificationRequest *vrq);
+
+	void (*_purple_reserved1)(void);
+	void (*_purple_reserved2)(void);
+	void (*_purple_reserved3)(void);
+	void (*_purple_reserved4)(void);
+};
+
+/** Structure for a single certificate request
+ *
+ *  Useful for keeping track of the state of a verification that involves
+ *  several steps
+ */
+struct _PurpleCertificateVerificationRequest
+{
+	/** Reference to the verification logic used */
+	PurpleCertificateVerifier *verifier;
+	/** Reference to the scheme used.
+	 *
+	 * This is looked up from the Verifier when the Request is generated
+	 */
+	PurpleCertificateScheme *scheme;
+
+	/**
+	 * Name to check that the certificate is issued to
+	 *
+	 * For X.509 certificates, this is the Common Name
+	 */
+	gchar *subject_name;
+	
+	/** List of certificates in the chain to be verified (such as that returned by purple_ssl_get_peer_certificates )
+	 *
+	 * This is most relevant for X.509 certificates used in SSL sessions.
+	 * The list order should be: certificate, issuer, issuer's issuer, etc.
+	 */
+	GList *cert_chain;
+	
+	/** Internal data used by the Verifier code */
+	gpointer data;
+
+	/** Function to call with the verification result */
+	PurpleCertificateVerifiedCallback cb;
+	/** Data to pass to the post-verification callback */
+	gpointer cb_data;
+};
+
+/*****************************************************************************/
+/** @name Certificate Verification Functions                                 */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Constructs a verification request and passed control to the specified Verifier
+ *
+ * It is possible that the callback will be called immediately upon calling
+ * this function. Plan accordingly.
+ *
+ * @param verifier      Verification logic to use.
+ *                      @see purple_certificate_find_verifier()
+ *
+ * @param subject_name  Name that should match the first certificate in the
+ *                      chain for the certificate to be valid. Will be strdup'd
+ *                      into the Request struct
+ *
+ * @param cert_chain    Certificate chain to check. If there is more than one
+ *                      certificate in the chain (X.509), the peer's
+ *                      certificate comes first, then the issuer/signer's
+ *                      certificate, etc. The whole list is duplicated into the
+ *                      Request struct.
+ *
+ * @param cb            Callback function to be called with whether the
+ *                      certificate was approved or not.
+ * @param cb_data       User-defined data for the above.
+ */
+void
+purple_certificate_verify (PurpleCertificateVerifier *verifier,
+			   const gchar *subject_name, GList *cert_chain,
+			   PurpleCertificateVerifiedCallback cb,
+			   gpointer cb_data);
+
+/**
+ * Completes and destroys a VerificationRequest
+ *
+ * @param vrq           Request to conclude
+ * @param st            Success/failure code to pass to the request's
+ *                      completion callback.
+ */
+void
+purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
+				   PurpleCertificateVerificationStatus st);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Functions                                              */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Makes a duplicate of a certificate
+ *
+ * @param crt        Instance to duplicate
+ * @return Pointer to new instance
+ */
+PurpleCertificate *
+purple_certificate_copy(PurpleCertificate *crt);
+
+/**
+ * Duplicates an entire list of certificates
+ *
+ * @param crt_list   List to duplicate
+ * @return New list copy
+ */
+GList *
+purple_certificate_copy_list(GList *crt_list);
+
+/**
+ * Destroys and free()'s a Certificate
+ *
+ * @param crt        Instance to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy (PurpleCertificate *crt);
+
+/**
+ * Destroy an entire list of Certificate instances and the containing list
+ *
+ * @param crt_list   List of certificates to destroy. May be NULL.
+ */
+void
+purple_certificate_destroy_list (GList * crt_list);
+
+/**
+ * Check whether 'crt' has a valid signature made by 'issuer'
+ *
+ * @param crt        Certificate instance to check signature of
+ * @param issuer     Certificate thought to have signed 'crt'
+ *
+ * @return TRUE if 'crt' has a valid signature made by 'issuer',
+ *         otherwise FALSE
+ * @TODO Find a way to give the reason (bad signature, not the issuer, etc.) 
+ */
+gboolean
+purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer);
+
+/**
+ * Check that a certificate chain is valid
+ *
+ * Uses purple_certificate_signed_by() to verify that each PurpleCertificate
+ * in the chain carries a valid signature from the next. A single-certificate
+ * chain is considered to be valid.
+ *
+ * @param chain      List of PurpleCertificate instances comprising the chain,
+ *                   in the order certificate, issuer, issuer's issuer, etc.
+ * @return TRUE if the chain is valid. See description.
+ * @TODO Specify which certificate in the chain caused a failure
+ */
+gboolean
+purple_certificate_check_signature_chain(GList *chain);
+
+/**
+ * Imports a PurpleCertificate from a file
+ *
+ * @param scheme      Scheme to import under
+ * @param filename    File path to import from
+ * @return Pointer to a new PurpleCertificate, or NULL on failure
+ */
+PurpleCertificate *
+purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename);
+
+/**
+ * Exports a PurpleCertificate to a file
+ *
+ * @param filename    File to export the certificate to
+ * @param crt         Certificate to export
+ * @return TRUE if the export succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_export(const gchar *filename, PurpleCertificate *crt);
+
+
+/**
+ * Retrieves the certificate public key fingerprint using SHA1.
+ *
+ * @param crt        Certificate instance
+ * @return Binary representation of the hash. You are responsible for free()ing
+ *         this.
+ * @see purple_base16_encode_chunked()
+ */
+GByteArray *
+purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate uniquely. Must be g_free()'ed
+ */
+gchar *
+purple_certificate_get_unique_id(PurpleCertificate *crt);
+
+/**
+ * Get a unique identifier for the certificate's issuer
+ *
+ * @param crt        Certificate instance
+ * @return String representing the certificate's issuer uniquely. Must be
+ *         g_free()'ed
+ */
+gchar *
+purple_certificate_get_issuer_unique_id(PurpleCertificate *crt);
+
+/**
+ * Gets the certificate subject's name
+ *
+ * For X.509, this is the "Common Name" field, as we're only using it
+ * for hostname verification at the moment
+ *
+ * @param crt   Certificate instance
+ * @return Newly allocated string with the certificate subject.
+ */
+gchar *
+purple_certificate_get_subject_name(PurpleCertificate *crt);
+
+/**
+ * Check the subject name against that on the certificate
+ * @param crt   Certificate instance
+ * @param name  Name to check. 
+ * @return TRUE if it is a match, else FALSE
+ */
+gboolean
+purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name);
+
+/**
+ * Get the expiration/activation times.
+ *
+ * @param crt          Certificate instance
+ * @param activation   Reference to store the activation time at. May be NULL
+ *                     if you don't actually want it.
+ * @param expiration   Reference to store the expiration time at. May be NULL
+ *                     if you don't actually want it.
+ * @return TRUE if the requested values were obtained, otherwise FALSE.
+ */
+gboolean
+purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Pool Functions                                         */
+/*****************************************************************************/
+/*@{*/
+/**
+ * Helper function for generating file paths in ~/.purple/certificates for
+ * CertificatePools that use them.
+ *
+ * All components will be escaped for filesystem friendliness.
+ *
+ * @param pool   CertificatePool to build a path for
+ * @param id     Key to look up a Certificate by. May be NULL.
+ * @return A newly allocated path of the form
+ *         ~/.purple/certificates/scheme_name/pool_name/unique_id
+ */
+gchar *
+purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Determines whether a pool can be used.
+ *
+ * Checks whether the associated CertificateScheme is loaded.
+ *
+ * @param pool   Pool to check
+ *
+ * @return TRUE if the pool can be used, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_usable(PurpleCertificatePool *pool);
+
+/**
+ * Looks up the scheme the pool operates under
+ *
+ * @param pool   Pool to get the scheme of
+ *
+ * @return Pointer to the pool's scheme, or NULL if it isn't loaded.
+ * @see purple_certificate_pool_usable()
+ */
+PurpleCertificateScheme *
+purple_certificate_pool_get_scheme(PurpleCertificatePool *pool);
+
+/**
+ * Check for presence of an ID in a pool.
+ * @param pool   Pool to look in
+ * @param id     ID to look for
+ * @return TRUE if the ID is in the pool, else FALSE
+ */
+gboolean
+purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Retrieve a certificate from a pool.
+ * @param pool   Pool to fish in
+ * @param id     ID to look up
+ * @return Retrieved certificate, or NULL if it wasn't there
+ */
+PurpleCertificate *
+purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Add a certificate to a pool
+ *
+ * Any pre-existing certificate of the same ID will be overwritten.
+ *
+ * @param pool   Pool to add to
+ * @param id     ID to store the certificate with
+ * @param crt    Certificate to store
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt);
+
+/**
+ * Remove a certificate from a pool
+ *
+ * @param pool   Pool to remove from
+ * @param id     ID to remove
+ * @return TRUE if the operation succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id);
+
+/**
+ * Get the list of IDs currently in the pool.
+ *
+ * @param pool   Pool to enumerate
+ * @return GList pointing to newly-allocated id strings. Free using
+ *         purple_certificate_pool_destroy_idlist()
+ */
+GList *
+purple_certificate_pool_get_idlist(PurpleCertificatePool *pool);
+
+/**
+ * Destroys the result given by purple_certificate_pool_get_idlist()
+ *
+ * @param idlist ID List to destroy
+ */
+void
+purple_certificate_pool_destroy_idlist(GList *idlist);
+
+/*@}*/
+
+/*****************************************************************************/
+/** @name Certificate Subsystem API                                          */
+/*****************************************************************************/
+/*@{*/
+
+/**
+ * Initialize the certificate system
+ */
+void
+purple_certificate_init(void);
+
+/**
+ * Un-initialize the certificate system
+ */
+void
+purple_certificate_uninit(void);
+
+/**
+ * Get the Certificate subsystem handle for signalling purposes
+ */
+gpointer
+purple_certificate_get_handle(void);
+
+/** Look up a registered CertificateScheme by name
+ * @param name   The scheme name. Case insensitive.
+ * @return Pointer to the located Scheme, or NULL if it isn't found.
+ */
+PurpleCertificateScheme *
+purple_certificate_find_scheme(const gchar *name);
+
+/**
+ * Get all registered CertificateSchemes
+ *
+ * @return GList pointing to all registered CertificateSchemes . This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_schemes(void);
+
+/** Register a CertificateScheme with libpurple
+ *
+ * No two schemes can be registered with the same name; this function enforces
+ * that.
+ *
+ * @param scheme  Pointer to the scheme to register.
+ * @return TRUE if the scheme was successfully added, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_scheme(PurpleCertificateScheme *scheme);
+
+/** Unregister a CertificateScheme from libpurple
+ *
+ * @param scheme    Scheme to unregister.
+ *                  If the scheme is not registered, this is a no-op.
+ *
+ * @return TRUE if the unregister completed successfully
+ */
+gboolean
+purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme);
+
+/** Look up a registered PurpleCertificateVerifier by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param ver_name     The verifier name. Case insensitive.
+ * @return Pointer to the located Verifier, or NULL if it isn't found.
+ */
+PurpleCertificateVerifier *
+purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name);
+
+/**
+ * Get the list of registered CertificateVerifiers
+ *
+ * @return GList of all registered PurpleCertificateVerifier. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_verifiers(void);
+
+/**
+ * Register a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to register.
+ * @return TRUE if register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_verifier(PurpleCertificateVerifier *vr);
+
+/**
+ * Unregister a CertificateVerifier with libpurple
+ *
+ * @param vr     Verifier to unregister.
+ * @return TRUE if unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr);
+
+/** Look up a registered PurpleCertificatePool by scheme and name
+ * @param scheme_name  Scheme name. Case insensitive.
+ * @param pool_name    Pool name. Case insensitive.
+ * @return Pointer to the located Pool, or NULL if it isn't found.
+ */
+PurpleCertificatePool *
+purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name);
+
+/**
+ * Get the list of registered Pools
+ *
+ * @return GList of all registered PurpleCertificatePool s. This value
+ *         is owned by libpurple
+ */
+GList *
+purple_certificate_get_pools(void);
+
+/**
+ * Register a CertificatePool with libpurple and call its init function
+ *
+ * @param pool   Pool to register.
+ * @return TRUE if the register succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_register_pool(PurpleCertificatePool *pool);
+
+/**
+ * Unregister a CertificatePool with libpurple and call its uninit function
+ *
+ * @param pool   Pool to unregister.
+ * @return TRUE if the unregister succeeded, otherwise FALSE
+ */
+gboolean
+purple_certificate_unregister_pool(PurpleCertificatePool *pool);
+
+/*@}*/
+
+
+/**
+ * Displays a window showing X.509 certificate information
+ *
+ * @param crt    Certificate under an "x509" Scheme
+ * @TODO Will break on CA certs, as they have no Common Name
+ */
+void
+purple_certificate_display_x509(PurpleCertificate *crt);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _PURPLE_CERTIFICATE_H */
--- a/libpurple/cipher.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/cipher.c	Wed Sep 12 19:11:38 2007 +0000
@@ -48,7 +48,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <glib.h>
 #include <string.h>
@@ -2021,7 +2021,7 @@
 		if (client_nonce == NULL)
 		{
 			purple_cipher_context_destroy(context);
-			purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation.");
+			purple_debug_error("cipher", "Required client_nonce missing for MD5-sess digest calculation.\n");
 			return NULL;
 		}
 
@@ -2091,7 +2091,7 @@
 		if (entity == NULL)
 		{
 			purple_cipher_context_destroy(context);
-			purple_debug_error("cipher", "Required entity missing for auth-int digest calculation.");
+			purple_debug_error("cipher", "Required entity missing for auth-int digest calculation.\n");
 			return NULL;
 		}
 
@@ -2118,14 +2118,14 @@
 		if (nonce_count == NULL)
 		{
 			purple_cipher_context_destroy(context);
-			purple_debug_error("cipher", "Required nonce_count missing for digest calculation.");
+			purple_debug_error("cipher", "Required nonce_count missing for digest calculation.\n");
 			return NULL;
 		}
 
 		if (client_nonce == NULL)
 		{
 			purple_cipher_context_destroy(context);
-			purple_debug_error("cipher", "Required client_nonce missing for digest calculation.");
+			purple_debug_error("cipher", "Required client_nonce missing for digest calculation.\n");
 			return NULL;
 		}
 
--- a/libpurple/cipher.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/cipher.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref cipher-signals
  */
--- a/libpurple/circbuffer.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/circbuffer.c	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 
--- a/libpurple/circbuffer.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/circbuffer.h	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _CIRCBUFFER_H
 #define _CIRCBUFFER_H
--- a/libpurple/cmds.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/cmds.c	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/cmds.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/cmds.h	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #ifndef _PURPLE_CMDS_H_
--- a/libpurple/connection.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/connection.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "account.h"
@@ -158,6 +158,62 @@
 }
 
 void
+purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data)
+{
+	/* Lots of copy/pasted code to avoid API changes. You might want to integrate that into the previous function when posssible. */
+	PurpleConnection *gc;
+	PurplePlugin *prpl;
+	PurplePluginProtocolInfo *prpl_info;
+	
+	g_return_if_fail(account != NULL);
+		
+	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
+	
+	if (prpl != NULL)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	else {
+		gchar *message;
+		
+		message = g_strdup_printf(_("Missing protocol plugin for %s"),
+								  purple_account_get_username(account));
+		purple_notify_error(NULL, _("Unregistration Error"), message, NULL);
+		g_free(message);
+		return;
+	}
+
+	if (!purple_account_is_disconnected(account)) {
+		prpl_info->unregister_user(account, cb, user_data);
+		return;
+	}
+	
+	if (((password == NULL) || (*password == '\0')) &&
+		!(prpl_info->options & OPT_PROTO_NO_PASSWORD) &&
+		!(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL))
+	{
+		purple_debug_error("connection", "Can not connect to account %s without "
+						   "a password.\n", purple_account_get_username(account));
+		return;
+	}
+	
+	gc = g_new0(PurpleConnection, 1);
+	PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection);
+	
+	gc->prpl = prpl;
+	if ((password != NULL) && (*password != '\0'))
+		gc->password = g_strdup(password);
+	purple_connection_set_account(gc, account);
+	purple_connection_set_state(gc, PURPLE_CONNECTING);
+	connections = g_list_append(connections, gc);
+	purple_account_set_connection(account, gc);
+	
+	purple_signal_emit(purple_connections_get_handle(), "signing-on", gc);
+	
+	purple_debug_info("connection", "Unregistering.  gc = %p\n", gc);
+	
+	prpl_info->unregister_user(account, cb, user_data);
+}
+
+void
 purple_connection_destroy(PurpleConnection *gc)
 {
 	PurpleAccount *account;
@@ -436,7 +492,7 @@
 	g_return_if_fail(gc   != NULL);
 
 	if (text == NULL) {
-		purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed");
+		purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed\n");
 		text = _("Unknown error");
 	}
 
--- a/libpurple/connection.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/connection.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref connection-signals
  */
@@ -171,6 +171,18 @@
 									const char *password);
 
 /**
+ * This function should only be called by purple_account_unregister()
+ * in account.c.
+ *
+ * Tries to unregister the account on the server. If the account is not
+ * connected, also creates a new connection.
+ *
+ * @param account  The account to unregister
+ * @param password The password to use.
+ */
+void purple_connection_new_unregister(PurpleAccount *account, const char *password, PurpleAccountUnregistrationCb cb, void *user_data);
+
+/**
  * Disconnects and destroys a PurpleConnection.
  *
  * This function should only be called by purple_account_disconnect()
--- a/libpurple/conversation.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/conversation.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "blist.h"
@@ -111,17 +111,17 @@
 
 	/* Always linkfy the text for display, unless we're
 	 * explicitly asked to do otheriwse*/
-	if(msgflags & PURPLE_MESSAGE_NO_LINKIFY)
-		displayed = g_strdup(message);
-	else
-		displayed = purple_markup_linkify(message);
-
-	if ((conv->features & PURPLE_CONNECTION_HTML) &&
-		!(msgflags & PURPLE_MESSAGE_RAW))
-	{
+	if (!(msgflags & PURPLE_MESSAGE_INVISIBLE)) {
+		if(msgflags & PURPLE_MESSAGE_NO_LINKIFY)
+			displayed = g_strdup(message);
+		else
+			displayed = purple_markup_linkify(message);
+	}
+
+	if (displayed && (conv->features & PURPLE_CONNECTION_HTML) &&
+		!(msgflags & PURPLE_MESSAGE_RAW)) {
 		sent = g_strdup(displayed);
-	}
-	else
+	} else
 		sent = g_strdup(message);
 
 	msgflags |= PURPLE_MESSAGE_SEND;
@@ -202,6 +202,48 @@
 							   conv, time(NULL), NULL));
 }
 
+/* Functions that deal with PurpleConvMessage */
+
+static void
+add_message_to_history(PurpleConversation *conv, const char *who, const char *message,
+		PurpleMessageFlags flags, time_t when)
+{
+	PurpleConvMessage *msg;
+
+	if (flags & PURPLE_MESSAGE_SEND) {
+		const char *me = NULL;
+		if (conv->account->gc)
+			me = conv->account->gc->display_name;
+		if (!me)
+			me = conv->account->username;
+		who = me;
+	}
+	
+	msg = g_new0(PurpleConvMessage, 1);
+	PURPLE_DBUS_REGISTER_POINTER(msg, PurpleConvMessage);
+	msg->who = g_strdup(who);
+	msg->flags = flags;
+	msg->what = g_strdup(message);
+	msg->when = when;
+
+	conv->message_history = g_list_prepend(conv->message_history, msg);
+}
+
+static void
+free_conv_message(PurpleConvMessage *msg)
+{
+	g_free(msg->who);
+	g_free(msg->what);
+	PURPLE_DBUS_UNREGISTER_POINTER(msg);
+	g_free(msg);
+}
+
+static void
+message_history_free(GList *list)
+{
+	g_list_foreach(list, (GFunc)free_conv_message, NULL);
+	g_list_free(list);
+}
 
 /**************************************************************************
  * Conversation API
@@ -473,6 +515,8 @@
 
 	purple_conversation_close_logs(conv);
 
+	purple_conversation_clear_message_history(conv);
+
 	PURPLE_DBUS_UNREGISTER_POINTER(conv);
 	g_free(conv);
 	conv = NULL;
@@ -805,9 +849,6 @@
 
 	ops = purple_conversation_get_ui_ops(conv);
 
-	if (ops == NULL || ops->write_conv == NULL)
-		return;
-
 	account = purple_conversation_get_account(conv);
 	type = purple_conversation_get_type(conv);
 
@@ -824,6 +865,10 @@
 
 	displayed = g_strdup(message);
 
+	if (who == NULL || *who == '\0')
+		who = purple_conversation_get_name(conv);
+	alias = who;
+
 	plugin_return =
 		GPOINTER_TO_INT(purple_signal_emit_return_1(
 			purple_conversations_get_handle(),
@@ -838,11 +883,6 @@
 		return;
 	}
 
-	if (who == NULL || *who == '\0')
-		who = purple_conversation_get_name(conv);
-
-	alias = who;
-
 	if (account != NULL) {
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
 
@@ -891,7 +931,9 @@
 		}
 	}
 
-	ops->write_conv(conv, who, alias, displayed, flags, mtime);
+	if (ops && ops->write_conv)
+		ops->write_conv(conv, who, alias, displayed, flags, mtime);
+	add_message_to_history(conv, who, message, flags, mtime);
 
 	purple_signal_emit(purple_conversations_get_handle(),
 		(type == PURPLE_CONV_TYPE_IM ? "wrote-im-msg" : "wrote-chat-msg"),
@@ -2020,6 +2062,42 @@
 	return menu;
 }
 
+void purple_conversation_clear_message_history(PurpleConversation *conv)
+{
+	GList *list = conv->message_history;
+	message_history_free(list);
+	conv->message_history = NULL;
+}
+
+GList *purple_conversation_get_message_history(PurpleConversation *conv)
+{
+	return conv->message_history;
+}
+
+const char *purple_conversation_message_get_sender(PurpleConvMessage *msg)
+{
+	g_return_val_if_fail(msg, NULL);
+	return msg->who;
+}
+
+const char *purple_conversation_message_get_message(PurpleConvMessage *msg)
+{
+	g_return_val_if_fail(msg, NULL);
+	return msg->what;
+}
+
+PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg)
+{
+	g_return_val_if_fail(msg, 0);
+	return msg->flags;
+}
+
+time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg)
+{
+	g_return_val_if_fail(msg, 0);
+	return msg->when;
+}
+
 gboolean
 purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline,
 				const gchar *markup, gchar **error)
@@ -2309,3 +2387,4 @@
 		purple_conversation_destroy((PurpleConversation*)conversations->data);
 	purple_signals_unregister_by_instance(purple_conversations_get_handle());
 }
+
--- a/libpurple/conversation.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/conversation.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref conversation-signals
  */
@@ -37,6 +37,7 @@
 typedef struct _PurpleConvIm            PurpleConvIm;
 typedef struct _PurpleConvChat          PurpleConvChat;
 typedef struct _PurpleConvChatBuddy     PurpleConvChatBuddy;
+typedef struct _PurpleConvMessage       PurpleConvMessage;
 
 /**
  * A type of conversation.
@@ -117,9 +118,9 @@
 	                                        apply formatting         */
 	PURPLE_MESSAGE_IMAGES      = 0x1000, /**< Message contains images  */
 	PURPLE_MESSAGE_NOTIFY      = 0x2000, /**< Message is a notification */
-	PURPLE_MESSAGE_NO_LINKIFY  = 0x4000  /**< Message should not be auto-
+	PURPLE_MESSAGE_NO_LINKIFY  = 0x4000, /**< Message should not be auto-
 										   linkified */
-
+	PURPLE_MESSAGE_INVISIBLE   = 0x8000, /**< Message should not be displayed */
 } PurpleMessageFlags;
 
 /**
@@ -183,7 +184,7 @@
 	                   time_t mtime);
 
 	/** Add @a cbuddies to a chat.
-	 *  @param cbuddies      A @C GList of #PurpleConvChatBuddy structs.
+	 *  @param cbuddies      A @c GList of #PurpleConvChatBuddy structs.
 	 *  @param new_arrivals  Whether join notices should be shown.
 	 *                       (Join notices are actually written to the
 	 *                       conversation by #purple_conv_chat_add_users().)
@@ -199,7 +200,7 @@
 	void (*chat_rename_user)(PurpleConversation *conv, const char *old_name,
 	                         const char *new_name, const char *new_alias);
 	/** Remove @a users from a chat.
-	 *  @param users    A @C GList of <tt>const char *</tt>s.
+	 *  @param users    A @c GList of <tt>const char *</tt>s.
 	 *  @see purple_conv_chat_rename_user()
 	 */
 	void (*chat_remove_users)(PurpleConversation *conv, GList *users);
@@ -283,6 +284,17 @@
 };
 
 /**
+ * Description of a conversation message
+ */
+struct _PurpleConvMessage
+{
+	char *who;
+	char *what;
+	PurpleMessageFlags flags;
+	time_t when;
+};
+
+/**
  * A core representation of a conversation between two or more people.
  *
  * The conversation can be an IM or a chat.
@@ -315,7 +327,7 @@
 	GHashTable *data;                        /**< Plugin-specific data.   */
 
 	PurpleConnectionFlags features; /**< The supported features */
-
+	GList *message_history;         /**< Message history, as a GList of PurpleConvMessage's */
 };
 
 #ifdef __cplusplus
@@ -650,6 +662,60 @@
  */
 void purple_conversation_foreach(void (*func)(PurpleConversation *conv));
 
+/**
+ * Retrieve the message history of a conversation.
+ *
+ * @param conv   The conversation
+ *
+ * @return  A GList of PurpleConvMessage's. The must not modify the list or the data within.
+ *          The list contains the newest message at the beginning, and the oldest message at
+ *          the end.
+ */
+GList *purple_conversation_get_message_history(PurpleConversation *conv);
+
+/**
+ * Clear the message history of a conversation.
+ *
+ * @param conv  The conversation
+ */
+void purple_conversation_clear_message_history(PurpleConversation *conv);
+
+/**
+ * Get the sender from a PurpleConvMessage
+ *
+ * @param msg   A PurpleConvMessage
+ *
+ * @return   The name of the sender of the message
+ */
+const char *purple_conversation_message_get_sender(PurpleConvMessage *msg);
+
+/**
+ * Get the message from a PurpleConvMessage
+ *
+ * @param msg   A PurpleConvMessage
+ *
+ * @return   The name of the sender of the message
+ */
+const char *purple_conversation_message_get_message(PurpleConvMessage *msg);
+
+/**
+ * Get the message-flags of a PurpleConvMessage
+ *
+ * @param msg   A PurpleConvMessage
+ *
+ * @return   The name of the sender of the message
+ */
+PurpleMessageFlags purple_conversation_message_get_flags(PurpleConvMessage *msg);
+
+/**
+ * Get the timestamp of a PurpleConvMessage
+ *
+ * @param msg   A PurpleConvMessage
+ *
+ * @return   The name of the sender of the message
+ */
+time_t purple_conversation_message_get_timestamp(PurpleConvMessage *msg);
+
 /*@}*/
 
 
--- a/libpurple/core.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/core.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,10 +20,11 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "cipher.h"
+#include "certificate.h"
 #include "connection.h"
 #include "conversation.h"
 #include "core.h"
@@ -141,6 +142,7 @@
 	purple_accounts_init();
 	purple_savedstatuses_init();
 	purple_notify_init();
+	purple_certificate_init();
 	purple_connections_init();
 	purple_conversations_init();
 	purple_blist_init();
@@ -159,9 +161,8 @@
 	/*
 	 * Call this early on to try to auto-detect our IP address and
 	 * hopefully save some time later.
-	 * TODO: do this here after purple_prefs_load() has been moved into purple_prefs_init()
 	 */
-	/*purple_network_get_my_ip(-1);*/
+	purple_network_get_my_ip(-1);
 
 	if (ops != NULL && ops->ui_init != NULL)
 		ops->ui_init();
@@ -192,6 +193,7 @@
 	purple_notify_uninit();
 	purple_conversations_uninit();
 	purple_connections_uninit();
+	purple_certificate_uninit();
 	purple_buddy_icons_uninit();
 	purple_accounts_uninit();
 	purple_savedstatuses_uninit();
--- a/libpurple/core.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/core.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref core-signals
  */
--- a/libpurple/dbus-analyze-functions.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dbus-analyze-functions.py	Wed Sep 12 19:11:38 2007 +0000
@@ -26,6 +26,11 @@
     "purple_conv_placement_get_current_func",
     "purple_conv_placement_set_current_func",
 
+    # Similar to the above:
+    "purple_account_set_register_callback",
+    "purple_account_unregister",
+    "purple_connection_new_unregister",
+
     # This is excluded because this script treats PurpleLogReadFlags*
     # as pointer to a struct, instead of a pointer to an enum.  This
     # causes a compilation error. Someone should fix this script.
@@ -66,6 +71,7 @@
     "purple_savedstatuses_get_all",
     "purple_status_type_get_attrs",
     "purple_presence_get_statuses",
+    "purple_conversation_get_message_history",
 ]
 
 pointer = "#pointer#"
--- a/libpurple/dbus-bindings.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dbus-bindings.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/dbus-purple.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dbus-purple.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/dbus-server.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dbus-server.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/dbus-server.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dbus-server.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref dbus-server-signals
  */
--- a/libpurple/debug.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/debug.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "debug.h"
 #include "internal.h"
--- a/libpurple/debug.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/debug.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_DEBUG_H_
 #define _PURPLE_DEBUG_H_
--- a/libpurple/desktopitem.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/desktopitem.c	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -48,8 +48,8 @@
  * 
  * You should have received a copy of the GNU Library General Public
  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02111-1301, USA.
  */
 
 #include <errno.h>
--- a/libpurple/desktopitem.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/desktopitem.h	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -48,8 +48,8 @@
  * 
  * You should have received a copy of the GNU Library General Public
  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02111-1301, USA.
  */
 
 #ifndef _PURPLE_DESKTOP_ITEM_H_
--- a/libpurple/dnsquery.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dnsquery.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/dnsquery.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dnsquery.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_DNSQUERY_H_
 #define _PURPLE_DNSQUERY_H_
@@ -45,16 +45,21 @@
 typedef void  (*PurpleDnsQueryFailedCallback) (PurpleDnsQueryData *query_data, const gchar *error_message);
 
 /**
- * DNS Request UI operations
+ * DNS Request UI operations;  UIs should implement this if they want to do DNS
+ * lookups themselves, rather than relying on the core.
+ *
+ * @see @ref ui-ops
  */
 typedef struct
 {
-	/* If implemented, the UI is responsible for DNS queries */
-	gboolean (*resolve_host)(PurpleDnsQueryData *query_data, PurpleDnsQueryResolvedCallback resolved_cb, PurpleDnsQueryFailedCallback failed_cb);
+	/** If implemented, the UI is responsible for DNS queries */
+	gboolean (*resolve_host)(PurpleDnsQueryData *query_data,
+	                         PurpleDnsQueryResolvedCallback resolved_cb,
+	                         PurpleDnsQueryFailedCallback failed_cb);
 
-	/* After destroy is called, query_data will be feed, so this must
-	 * cancel any further use of it the UI would do. Unneeded if 
-	 * resolve_host is not implemented.
+	/** Called just before @a query_data is freed; this should cancel any
+	 *  further use of @q query_data the UI would make. Unneeded if
+	 *  #resolve_host is not implemented.
 	 */
 	void (*destroy)(PurpleDnsQueryData *query_data);
 
--- a/libpurple/dnssrv.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dnssrv.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/dnssrv.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/dnssrv.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_DNSSRV_H
--- a/libpurple/eventloop.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/eventloop.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "eventloop.h"
 #include "internal.h"
--- a/libpurple/eventloop.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/eventloop.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_EVENTLOOP_H_
 #define _PURPLE_EVENTLOOP_H_
--- a/libpurple/example/nullclient.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/example/nullclient.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/ft.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/ft.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
--- a/libpurple/ft.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/ft.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref xfer-signals
  */
--- a/libpurple/gaim-compat.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/gaim-compat.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref account-signals
  */
@@ -1654,7 +1654,7 @@
 #define gaim_request_choice_varg        purple_request_choice_varg
 #define gaim_request_action             purple_request_action
 #define gaim_request_action_varg        purple_request_action_varg
-#define gaim_request_fields             purple_request_fields
+#define gaim_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, user_data)             purple_request_fields(handle, title, primary, secondary, fields, ok_text, ok_cb, cancel_text, cancel_cb, NULL, NULL, NULL, user_data)
 #define gaim_request_close              purple_request_close
 #define gaim_request_close_with_handle  purple_request_close_with_handle
 
--- a/libpurple/idle.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/idle.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -163,8 +163,8 @@
 		{
 			if (!no_away)
 			{
+				no_away = TRUE;
 				purple_savedstatus_set_idleaway(FALSE);
-				no_away = TRUE;
 			}
 			time_until_next_idle_event = 0;
 			return;
--- a/libpurple/idle.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/idle.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_IDLE_H_
 #define _PURPLE_IDLE_H_
--- a/libpurple/imgstore.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/imgstore.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
 */
 
--- a/libpurple/imgstore.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/imgstore.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref imgstore-signals
  */
--- a/libpurple/internal.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/internal.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_INTERNAL_H_
 #define _PURPLE_INTERNAL_H_
@@ -29,6 +29,10 @@
 # include <config.h>
 #endif
 
+/* for SIOCGIFCONF  in SKYOS */
+#ifdef SKYOS
+#include <net/sockios.h>
+#endif
 /*
  * If we're using NLS, make sure gettext works.  If not, then define
  * dummy macros in place of the normal gettext macros.
--- a/libpurple/log.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/log.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
@@ -1380,7 +1380,7 @@
 				written += fprintf(data->file, "<font color=\"#16569E\"><font size=\"2\">(%s)</font> <b>%s:</b></font> %s<br/>\n",
 						date, from, msg_fixed);
 		} else {
-			purple_debug_error("log", "Unhandled message type.");
+			purple_debug_error("log", "Unhandled message type.\n");
 			written += fprintf(data->file, "<font size=\"2\">(%s)</font><b> %s:</b></font> %s<br/>\n",
 						date, from, msg_fixed);
 		}
--- a/libpurple/log.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/log.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref log-signals
  */
--- a/libpurple/mime.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/mime.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  * USA.
  */
 
--- a/libpurple/mime.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/mime.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  * USA.
  */
 
--- a/libpurple/nat-pmp.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/nat-pmp.c	Wed Sep 12 19:11:38 2007 +0000
@@ -175,20 +175,20 @@
 	/* Determine the buffer side needed to get the full routing table */
     if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 
 	{
-		purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate");
+		purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump estimate\n");
 		return NULL;
     }
 
     if (!(buf = malloc(needed)))
 	{
-		purple_debug_warning("nat-pmp", "malloc");
+		purple_debug_warning("nat-pmp", "malloc\n");
 		return NULL;
     }
 
 	/* Read the routing table into buf */
     if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 
 	{
-		purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump");
+		purple_debug_warning("nat-pmp", "sysctl: net.route.0.0.dump\n");
 		return NULL;
     }
 
@@ -231,7 +231,7 @@
 						sin->sin_addr.s_addr = rti_sin->sin_addr.s_addr;
 						memcpy(sin, rti_info[RTAX_GATEWAY], sizeof(struct sockaddr_in));
 
-						purple_debug_info("nat-pmp", "found a default gateway");
+						purple_debug_info("nat-pmp", "found a default gateway\n");
 						found = TRUE;
 						break;
 					}
@@ -266,7 +266,7 @@
 	if ((pmp_info.status == PURPLE_PMP_STATUS_DISCOVERED) && (pmp_info.publicip != NULL))
 	{
 #ifdef PMP_DEBUG
-		purple_debug_info("nat-pmp", "Returning cached publicip %s",pmp_info.publicip);
+		purple_debug_info("nat-pmp", "Returning cached publicip %s\n",pmp_info.publicip);
 #endif
 		return pmp_info.publicip;
 	}
--- a/libpurple/network.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/network.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
@@ -351,12 +351,13 @@
 	listen_data->retry = TRUE;
 	listen_data->cb = cb;
 	listen_data->cb_data = cb_data;
+	listen_data->socket_type = socket_type;
 
 	/* Attempt a NAT-PMP Mapping, which will return immediately */
 	if (purple_pmp_create_map(((socket_type == SOCK_STREAM) ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP),
 							  actual_port, actual_port, PURPLE_PMP_LIFETIME))
 	{
-		purple_debug_info("network", "Created NAT-PMP mapping on port %i",actual_port);
+		purple_debug_info("network", "Created NAT-PMP mapping on port %i\n",actual_port);
 		/* We want to return listen_data now, and on the next run loop trigger the cb and destroy listen_data */
 		purple_timeout_add(0, purple_network_finish_pmp_map_cb, listen_data);
 	}
@@ -436,7 +437,7 @@
 static gint
 wpurple_get_connected_network_count(void)
 {
-	guint net_cnt = 0;
+	gint net_cnt = 0;
 
 	WSAQUERYSET qs;
 	HANDLE h;
@@ -521,7 +522,7 @@
 		HANDLE hLookup, DWORD dwControlCode, LPVOID lpvInBuffer,
 		DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,
 		LPDWORD lpcbBytesReturned, LPWSACOMPLETION lpCompletion) = NULL;
- 
+
 	if (!(MyWSANSPIoctl = (void*) wpurple_find_and_loadproc("ws2_32.dll", "WSANSPIoctl"))) {
 		g_thread_exit(NULL);
 		return NULL;
@@ -636,7 +637,7 @@
 purple_network_get_handle(void)
 {
 	static int handle;
-	
+
 	return &handle;
 }
 
@@ -675,7 +676,7 @@
 
 	purple_signal_register(purple_network_get_handle(), "network-configuration-changed",
 						   purple_marshal_VOID, NULL, 0);
-	
+
 	purple_pmp_init();
 	purple_upnp_init();
 }
--- a/libpurple/network.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/network.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_NETWORK_H_
 #define _PURPLE_NETWORK_H_
--- a/libpurple/notify.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/notify.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "dbus-maybe.h"
--- a/libpurple/notify.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/notify.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref notify-signals
  */
@@ -213,6 +213,11 @@
 								PurpleNotifySearchResults *results, PurpleNotifyCloseCallback cb,
 								gpointer user_data);
 
+/**
+ * Frees a PurpleNotifySearchResults object.
+ *
+ * @param results The PurpleNotifySearchResults to free.
+ */
 void purple_notify_searchresults_free(PurpleNotifySearchResults *results);
 
 /**
--- a/libpurple/ntlm.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/ntlm.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <glib.h>
--- a/libpurple/ntlm.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/ntlm.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_NTLM_H
--- a/libpurple/plugin.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugin.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 
--- a/libpurple/plugin.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugin.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * @see @ref plugin-signals
  * @see @ref plugin-ids
@@ -188,6 +188,8 @@
 	/** NULL for plugin actions menu, set to the PurpleConnection for
 	    account actions menu */
 	gpointer context;
+	
+	gpointer user_data;
 };
 
 #define PURPLE_PLUGIN_HAS_ACTIONS(plugin) \
--- a/libpurple/pluginpref.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/pluginpref.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifdef HAVE_CONFIG_H
 # include <config.h>
--- a/libpurple/pluginpref.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/pluginpref.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #ifndef _PURPLE_PLUGINPREF_H_
--- a/libpurple/plugins/autoaccept.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/autoaccept.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,8 +14,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 #include "internal.h"
 
--- a/libpurple/plugins/buddynote.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/buddynote.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,7 +14,7 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 #include "internal.h"
 
--- a/libpurple/plugins/ciphertest.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ciphertest.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,8 +15,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 
 #ifdef HAVE_CONFIG_H
--- a/libpurple/plugins/codeinline.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/codeinline.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/plugins/dbus-buddyicons-example.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/dbus-buddyicons-example.py	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
 #
 # 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 #
 
 import dbus
--- a/libpurple/plugins/dbus-example.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/dbus-example.c	Wed Sep 12 19:11:38 2007 +0000
@@ -32,7 +32,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/plugins/fortuneprofile.pl	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/fortuneprofile.pl	Wed Sep 12 19:11:38 2007 +0000
@@ -40,7 +40,7 @@
 #
 #    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301 USA
 
 use Gaim;
 
--- a/libpurple/plugins/idle.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/idle.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/plugins/ipc-test-client.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ipc-test-client.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,8 +15,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 #include "internal.h"
 #include "debug.h"
--- a/libpurple/plugins/ipc-test-server.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ipc-test-server.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,8 +15,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 #define IPC_TEST_SERVER_PLUGIN_ID "core-ipc-test-server"
 
--- a/libpurple/plugins/joinpart.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/joinpart.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/plugins/newline.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/newline.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,7 +14,7 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 #include "internal.h"
 
--- a/libpurple/plugins/offlinemsg.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/offlinemsg.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,8 +14,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 #include "internal.h"
 
@@ -113,6 +113,10 @@
 	PurpleConversation *conv;
 	OfflineMessageSetting setting;
 
+	if (message == NULL || *message == NULL ||
+			**message == '\0')
+		return;
+
 	buddy = purple_find_buddy(account, who);
 	if (!buddy)
 		return;
@@ -122,7 +126,7 @@
 
 	if (purple_account_supports_offline_message(account, buddy))
 	{
-		purple_debug_info("offlinemsg", "Account \"%s\" supports offline message.",
+		purple_debug_info("offlinemsg", "Account \"%s\" supports offline messages.\n",
 					purple_account_get_username(account));
 		return;
 	}
@@ -167,8 +171,8 @@
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
-	purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg",
-					plugin, PURPLE_CALLBACK(sending_msg_cb), plugin);
+	purple_signal_connect_priority(purple_conversations_get_handle(), "sending-im-msg",
+					plugin, PURPLE_CALLBACK(sending_msg_cb), plugin, PURPLE_SIGNAL_PRIORITY_HIGHEST);
 	return TRUE;
 }
 
--- a/libpurple/plugins/perl/perl.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/perl/perl.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifdef HAVE_CONFIG_H
 #include <config.h>
--- a/libpurple/plugins/pluginpref_example.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/pluginpref_example.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,8 +15,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 
 #ifdef HAVE_CONFIG_H
--- a/libpurple/plugins/psychic.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/psychic.c	Wed Sep 12 19:11:38 2007 +0000
@@ -49,7 +49,7 @@
   }
 
   if(FALSE == purple_privacy_check(acct, name)) {
-    purple_debug_info("psychic", "user %s is blocked", name);
+    purple_debug_info("psychic", "user %s is blocked\n", name);
     return;
   }
 
--- a/libpurple/plugins/signals-test.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/signals-test.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,8 +15,8 @@
  *
  * 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., 59 Temple Place - Suite 330, Boston, MA
- * 02111-1307, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
  */
 #define SIGNAL_TEST_PLUGIN_ID "core-signals-test"
 
--- a/libpurple/plugins/ssl/Makefile.mingw	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ssl/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -18,6 +18,7 @@
 			$(NSS_TOP)/lib/nss3.dll \
 			$(NSS_TOP)/lib/nssckbi.dll \
 			$(NSS_TOP)/lib/softokn3.dll \
+			$(NSS_TOP)/lib/smime3.dll \
 			$(NSS_TOP)/lib/ssl3.dll \
 			$(NSPR_TOP)/lib/nspr4.dll \
 			$(NSPR_TOP)/lib/plc4.dll \
@@ -59,7 +60,8 @@
 			-lpurple \
 			-lnss3 \
 			-lnspr4 \
-			-lssl3
+			-lssl3 \
+			-lsmime3
 
 include $(PIDGIN_COMMON_RULES)
 
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,19 +17,22 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
+#include "util.h"
 
 #define SSL_GNUTLS_PLUGIN_ID "ssl-gnutls"
 
 #ifdef HAVE_GNUTLS
 
 #include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
 
 typedef struct
 {
@@ -44,9 +47,25 @@
 static void
 ssl_gnutls_init_gnutls(void)
 {
+	/* Configure GnuTLS to use glib memory management */
+	/* I expect that this isn't really necessary, but it may prevent
+	   some bugs */
+	/* TODO: It may be necessary to wrap this allocators for GnuTLS.
+	   If there are strange bugs, perhaps look here (yes, I am a
+	   hypocrite) */
+	gnutls_global_set_mem_functions(
+		(gnutls_alloc_function)   g_malloc0, /* malloc */
+		(gnutls_alloc_function)   g_malloc0, /* secure malloc */
+		NULL,      /* mem_is_secure */
+		(gnutls_realloc_function) g_realloc, /* realloc */
+		(gnutls_free_function)    g_free     /* free */
+		);
+
 	gnutls_global_init();
 
 	gnutls_certificate_allocate_credentials(&xcred);
+
+	/* TODO: I can likely remove this */
 	gnutls_certificate_set_x509_trust_file(xcred, "ca.pem",
 		GNUTLS_X509_FMT_PEM);
 }
@@ -54,7 +73,7 @@
 static gboolean
 ssl_gnutls_init(void)
 {
-   return TRUE;
+	return TRUE;
 }
 
 static void
@@ -65,6 +84,25 @@
 	gnutls_certificate_free_credentials(xcred);
 }
 
+static void
+ssl_gnutls_verified_cb(PurpleCertificateVerificationStatus st,
+		       gpointer userdata)
+{
+	PurpleSslConnection *gsc = (PurpleSslConnection *) userdata;
+
+	if (st == PURPLE_CERTIFICATE_VALID) {
+		/* Certificate valid? Good! Do the connection! */
+		gsc->connect_cb(gsc->connect_cb_data, gsc, PURPLE_INPUT_READ);
+	} else {
+		/* Otherwise, signal an error */
+		if(gsc->error_cb != NULL)
+			gsc->error_cb(gsc, PURPLE_SSL_CERTIFICATE_INVALID,
+				      gsc->connect_cb_data);
+		purple_ssl_close(gsc);
+	}
+}
+
+
 
 static void ssl_gnutls_handshake_cb(gpointer data, gint source,
 		PurpleInputCondition cond)
@@ -73,7 +111,7 @@
 	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
 	ssize_t ret;
 
-	purple_debug_info("gnutls", "Handshaking\n");
+	purple_debug_info("gnutls", "Handshaking with %s\n", gsc->host);
 	ret = gnutls_handshake(gnutls_data->session);
 
 	if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
@@ -92,9 +130,119 @@
 
 		purple_ssl_close(gsc);
 	} else {
+		/* Now we are cooking with gas! */
+		PurpleSslOps *ops = purple_ssl_get_ops();
+		GList * peers = ops->get_peer_certificates(gsc);
+
+		PurpleCertificateScheme *x509 =
+			purple_certificate_find_scheme("x509");
+
+		GList * l;
+
+		/* TODO: Remove all this debugging babble */
 		purple_debug_info("gnutls", "Handshake complete\n");
 
-		gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		for (l=peers; l; l = l->next) {
+			PurpleCertificate *crt = l->data;
+			GByteArray *z =
+				x509->get_fingerprint_sha1(crt);
+			gchar * fpr =
+				purple_base16_encode_chunked(z->data,
+							     z->len);
+
+			purple_debug_info("gnutls/x509",
+					  "Key print: %s\n",
+					  fpr);
+
+			/* Kill the cert! */
+			x509->destroy_certificate(crt);
+
+			g_free(fpr);
+			g_byte_array_free(z, TRUE);
+		}
+		g_list_free(peers);
+
+		{
+			const gnutls_datum_t *cert_list;
+			unsigned int cert_list_size = 0;
+			gnutls_session_t session=gnutls_data->session;
+			int i;
+
+			cert_list =
+				gnutls_certificate_get_peers(session, &cert_list_size);
+
+			purple_debug_info("gnutls",
+					    "Peer provided %d certs\n",
+					    cert_list_size);
+			for (i=0; i<cert_list_size; i++)
+			{
+				gchar fpr_bin[256];
+				gsize fpr_bin_sz = sizeof(fpr_bin);
+				gchar * fpr_asc = NULL;
+				gchar tbuf[256];
+				gsize tsz=sizeof(tbuf);
+				gchar * tasc = NULL;
+				gnutls_x509_crt_t cert;
+
+				gnutls_x509_crt_init(&cert);
+				gnutls_x509_crt_import (cert, &cert_list[i],
+						GNUTLS_X509_FMT_DER);
+
+				gnutls_x509_crt_get_fingerprint(cert, GNUTLS_MAC_SHA,
+						fpr_bin, &fpr_bin_sz);
+
+				fpr_asc =
+						purple_base16_encode_chunked((const guchar *)fpr_bin, fpr_bin_sz);
+
+				purple_debug_info("gnutls",
+						"Lvl %d SHA1 fingerprint: %s\n",
+						i, fpr_asc);
+
+				tsz=sizeof(tbuf);
+				gnutls_x509_crt_get_serial(cert,tbuf,&tsz);
+				tasc=purple_base16_encode_chunked((const guchar *)tbuf, tsz);
+				purple_debug_info("gnutls",
+						"Serial: %s\n",
+						tasc);
+				g_free(tasc);
+
+				tsz=sizeof(tbuf);
+				gnutls_x509_crt_get_dn (cert, tbuf, &tsz);
+				purple_debug_info("gnutls",
+						"Cert DN: %s\n",
+						tbuf);
+				tsz=sizeof(tbuf);
+				gnutls_x509_crt_get_issuer_dn (cert, tbuf, &tsz);
+				purple_debug_info("gnutls",
+						"Cert Issuer DN: %s\n",
+						tbuf);
+
+				g_free(fpr_asc);
+				fpr_asc = NULL;
+				gnutls_x509_crt_deinit(cert);
+			}
+		}
+
+		/* TODO: The following logic should really be in libpurple */
+		/* If a Verifier was given, hand control over to it */
+		if (gsc->verifier) {
+			GList *peers;
+			/* First, get the peer cert chain */
+			peers = purple_ssl_get_peer_certificates(gsc);
+
+			/* Now kick off the verification process */
+			purple_certificate_verify(gsc->verifier,
+						  gsc->host,
+						  peers,
+						  ssl_gnutls_verified_cb,
+						  gsc);
+
+			purple_certificate_destroy_list(peers);
+		} else {
+			/* Otherwise, just call the "connection complete"
+			   callback */
+			gsc->connect_cb(gsc->connect_cb_data, gsc, cond);
+		}
 	}
 
 }
@@ -213,6 +361,565 @@
 	return s;
 }
 
+/* Forward declarations are fun! */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode);
+
+static GList *
+ssl_gnutls_get_peer_certificates(PurpleSslConnection * gsc)
+{
+	PurpleSslGnutlsData *gnutls_data = PURPLE_SSL_GNUTLS_DATA(gsc);
+
+	/* List of Certificate instances to return */
+	GList * peer_certs = NULL;
+
+	/* List of raw certificates as given by GnuTLS */
+	const gnutls_datum_t *cert_list;
+	unsigned int cert_list_size = 0;
+
+	unsigned int i;
+
+	/* This should never, ever happen. */
+	g_return_val_if_fail( gnutls_certificate_type_get (gnutls_data->session) == GNUTLS_CRT_X509, NULL);
+
+	/* Get the certificate list from GnuTLS */
+	/* TODO: I am _pretty sure_ this doesn't block or do other exciting things */
+	cert_list = gnutls_certificate_get_peers(gnutls_data->session,
+						 &cert_list_size);
+
+	/* Convert each certificate to a Certificate and append it to the list */
+	for (i = 0; i < cert_list_size; i++) {
+		PurpleCertificate * newcrt = x509_import_from_datum(cert_list[i],
+							      GNUTLS_X509_FMT_DER);
+		/* Append is somewhat inefficient on linked lists, but is easy
+		   to read. If someone complains, I'll change it.
+		   TODO: Is anyone complaining? (Maybe elb?) */
+		peer_certs = g_list_append(peer_certs, newcrt);
+	}
+
+	/* cert_list doesn't need free()-ing */
+
+	return peer_certs;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+const gchar * SCHEME_NAME = "x509";
+
+static PurpleCertificateScheme x509_gnutls;
+
+/** Refcounted GnuTLS certificate data instance */
+typedef struct {
+	gint refcount;
+	gnutls_x509_crt_t crt;
+} x509_crtdata_t;
+
+/** Helper functions for reference counting */
+static x509_crtdata_t *
+x509_crtdata_addref(x509_crtdata_t *cd)
+{
+	(cd->refcount)++;
+	return cd;
+}
+
+static void
+x509_crtdata_delref(x509_crtdata_t *cd)
+{
+	(cd->refcount)--;
+
+	if (cd->refcount < 0)
+		g_critical("Refcount of x509_crtdata_t is %d, which is less "
+				"than zero!\n", cd->refcount);
+
+	/* If the refcount reaches zero, kill the structure */
+	if (cd->refcount <= 0) {
+		purple_debug_info("gnutls/x509",
+				  "Freeing unused cert data at %p\n",
+				  cd);
+		/* Kill the internal data */
+		gnutls_x509_crt_deinit( cd->crt );
+		/* And kill the struct */
+		g_free( cd );
+	}
+}
+
+/** Helper macro to retrieve the GnuTLS crt_t from a PurpleCertificate */
+#define X509_GET_GNUTLS_DATA(pcrt) ( ((x509_crtdata_t *) (pcrt->data))->crt)
+
+/** Transforms a gnutls_datum_t containing an X.509 certificate into a Certificate instance under the x509_gnutls scheme
+ *
+ * @param dt   Datum to transform
+ * @param mode GnuTLS certificate format specifier (GNUTLS_X509_FMT_PEM for
+ *             reading from files, and GNUTLS_X509_FMT_DER for converting
+ *             "over the wire" certs for SSL)
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_datum(const gnutls_datum_t dt, gnutls_x509_crt_fmt_t mode)
+{
+	/* Internal certificate data structure */
+	x509_crtdata_t *certdat;
+	/* New certificate to return */
+	PurpleCertificate * crt;
+
+	/* Allocate and prepare the internal certificate data */
+	certdat = g_new0(x509_crtdata_t, 1);
+	gnutls_x509_crt_init(&(certdat->crt));
+	certdat->refcount = 0;
+
+	/* Perform the actual certificate parse */
+	/* Yes, certdat->crt should be passed as-is */
+	gnutls_x509_crt_import(certdat->crt, &dt, mode);
+
+	/* Allocate the certificate and load it with data */
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_gnutls;
+	crt->data = x509_crtdata_addref(certdat);
+
+	return crt;
+}
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar * filename)
+{
+	PurpleCertificate *crt;  /* Certificate being constructed */
+	gchar *buf;        /* Used to load the raw file data */
+	gsize buf_sz;      /* Size of the above */
+	gnutls_datum_t dt; /* Struct to pass down to GnuTLS */
+
+	purple_debug_info("gnutls",
+			  "Attempting to load X.509 certificate from %s\n",
+			  filename);
+
+	/* Next, we'll simply yank the entire contents of the file
+	   into memory */
+	/* TODO: Should I worry about very large files here? */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+			    &buf,
+			    &buf_sz,
+			    NULL      /* No error checking for now */
+		),
+		NULL);
+
+	/* Load the datum struct */
+	dt.data = (unsigned char *) buf;
+	dt.size = buf_sz;
+
+	/* Perform the conversion */
+	crt = x509_import_from_datum(dt,
+				     GNUTLS_X509_FMT_PEM); // files should be in PEM format
+
+	/* Cleanup */
+	g_free(buf);
+
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t crt_dat; /* GnuTLS cert struct */
+	int ret;
+	gchar * out_buf; /* Data to output */
+	size_t out_size; /* Output size */
+	gboolean success = FALSE;
+
+	/* Paranoia paranoia paranoia! */
+	g_return_val_if_fail(filename, FALSE);
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(crt->data, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Obtain the output size required */
+	out_size = 0;
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     NULL, /* Provide no buffer yet */
+				     &out_size /* Put size here */
+		);
+	g_return_val_if_fail(ret == GNUTLS_E_SHORT_MEMORY_BUFFER, FALSE);
+
+	/* Now allocate a buffer and *really* export it */
+	out_buf = g_new0(gchar, out_size);
+	ret = gnutls_x509_crt_export(crt_dat, GNUTLS_X509_FMT_PEM,
+				     out_buf, /* Export to our new buffer */
+				     &out_size /* Put size here */
+		);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to export cert to buffer with code %d\n",
+				   ret);
+		g_free(out_buf);
+		return FALSE;
+	}
+
+	/* Write it out to an actual file */
+	success = purple_util_write_data_to_file_absolute(filename,
+							  out_buf, out_size);
+
+	g_free(out_buf);
+	g_return_val_if_fail(success, FALSE);
+	return success;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	x509_crtdata_t *crtdat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	crtdat = (x509_crtdata_t *) crt->data;
+
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_gnutls;
+	newcrt->data = x509_crtdata_addref(crtdat);
+
+	return newcrt;
+}
+/** Frees a Certificate
+ *
+ * Destroys a Certificate's internal data structures and frees the pointer
+ * given.
+ * @param crt Certificate instance to be destroyed. It WILL NOT be destroyed
+ *            if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	if (NULL == crt) return;
+
+	/* Check that the scheme is x509_gnutls */
+	if ( crt->scheme != &x509_gnutls ) {
+		purple_debug_error("gnutls",
+				   "destroy_certificate attempted on certificate of wrong scheme (scheme was %s, expected %s)\n",
+				   crt->scheme->name,
+				   SCHEME_NAME);
+		return;
+	}
+
+	g_return_if_fail(crt->data != NULL);
+	g_return_if_fail(crt->scheme != NULL);
+
+	/* Use the reference counting system to free (or not) the
+	   underlying data */
+	x509_crtdata_delref((x509_crtdata_t *)crt->data);
+
+	/* Kill the structure itself */
+	g_free(crt);
+}
+
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	gnutls_x509_crt_t crt_dat;
+	gnutls_x509_crt_t issuer_dat;
+	unsigned int verify; /* used to store result from GnuTLS verifier */
+	int ret;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(issuer, FALSE);
+
+	/* Verify that both certs are the correct scheme */
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(issuer->scheme == &x509_gnutls, FALSE);
+
+	/* TODO: check for more nullness? */
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+	issuer_dat = X509_GET_GNUTLS_DATA(issuer);
+
+	/* First, let's check that crt.issuer is actually issuer */
+	ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
+	if (ret <= 0) {
+
+		if (ret < 0) {
+			purple_debug_error("gnutls/x509",
+					   "GnuTLS error %d while checking certificate issuer match.",
+					   ret);
+		} else {
+			gchar *crt_id, *issuer_id, *crt_issuer_id;
+			crt_id = purple_certificate_get_unique_id(crt);
+			issuer_id = purple_certificate_get_unique_id(issuer);
+			crt_issuer_id =
+				purple_certificate_get_issuer_unique_id(crt);
+			purple_debug_info("gnutls/x509",
+					  "Certificate for %s claims to be "
+					  "issued by %s, but the certificate "
+					  "for %s does not match. A strcmp "
+					  "says %d\n",
+					  crt_id, crt_issuer_id, issuer_id,
+					  strcmp(crt_issuer_id, issuer_id));
+			g_free(crt_id);
+			g_free(issuer_id);
+			g_free(crt_issuer_id);
+		}
+
+		/* The issuer is not correct, or there were errors */
+		return FALSE;
+	}
+
+	/* Now, check the signature */
+	/* The second argument is a ptr to an array of "trusted" issuer certs,
+	   but we're only using one trusted one */
+	ret = gnutls_x509_crt_verify(crt_dat, &issuer_dat, 1,
+				     /* Permit signings by X.509v1 certs
+					(Verisign and possibly others have
+					root certificates that predate the
+					current standard) */
+				     GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
+				     &verify);
+
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Attempted certificate verification caused a GnuTLS error code %d. I will just say the signature is bad, but you should look into this.\n", ret);
+		return FALSE;
+	}
+
+	if (verify & GNUTLS_CERT_INVALID) {
+		/* Signature didn't check out, but at least
+		   there were no errors*/
+		gchar *crt_id = purple_certificate_get_unique_id(crt);
+		gchar *issuer_id = purple_certificate_get_issuer_unique_id(crt);
+		purple_debug_info("gnutls/x509",
+				  "Bad signature for %s on %s\n",
+				  issuer_id, crt_id);
+		g_free(crt_id);
+		g_free(issuer_id);
+
+		return FALSE;
+	} /* if (ret, etc.) */
+
+	/* If we got here, the signature is good */
+	return TRUE;
+}
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	size_t hashlen = 20; /* SHA1 hashes are 20 bytes */
+	size_t tmpsz = hashlen; /* Throw-away variable for GnuTLS to stomp on*/
+	gnutls_x509_crt_t crt_dat;
+	GByteArray *hash; /**< Final hash container */
+	guchar hashbuf[hashlen]; /**< Temporary buffer to contain hash */
+
+	g_return_val_if_fail(crt, NULL);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Extract the fingerprint */
+	g_return_val_if_fail(
+		0 == gnutls_x509_crt_get_fingerprint(crt_dat, GNUTLS_MAC_SHA,
+						     hashbuf, &tmpsz),
+		NULL);
+
+	/* This shouldn't happen */
+	g_return_val_if_fail(tmpsz == hashlen, NULL);
+
+	/* Okay, now create and fill hash array */
+	hash = g_byte_array_new();
+	g_byte_array_append(hash, hashbuf, hashlen);
+
+	return hash;
+}
+
+static gchar *
+x509_cert_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	dn = g_new0(gchar, dn_size);
+	if (0 != gnutls_x509_crt_get_dn(cert_dat, dn, &dn_size)) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get Distinguished Name\n");
+		g_free(dn);
+		return NULL;
+	}
+
+	return dn;
+}
+
+static gchar *
+x509_issuer_dn (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *dn = NULL;
+	size_t dn_size;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Distinguished Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	dn_size = 0;
+	gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size);
+
+	/* Now allocate and get the Distinguished Name */
+	dn = g_new0(gchar, dn_size);
+	if (0 != gnutls_x509_crt_get_issuer_dn(cert_dat, dn, &dn_size)) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get issuer's Distinguished "
+				   "Name\n");
+		g_free(dn);
+		return NULL;
+	}
+
+	return dn;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	gnutls_x509_crt_t cert_dat;
+	gchar *cn = NULL;
+	size_t cn_size;
+	int ret;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, NULL);
+
+	cert_dat = X509_GET_GNUTLS_DATA(crt);
+
+	/* Figure out the length of the Common Name */
+	/* Claim that the buffer is size 0 so GnuTLS just tells us how much
+	   space it needs */
+	cn_size = 0;
+	gnutls_x509_crt_get_dn_by_oid(cert_dat,
+				      GNUTLS_OID_X520_COMMON_NAME,
+				      0, /* First CN found, please */
+				      0, /* Not in raw mode */
+				      cn, &cn_size);
+
+	/* Now allocate and get the Common Name */
+	cn = g_new0(gchar, cn_size);
+	ret = gnutls_x509_crt_get_dn_by_oid(cert_dat,
+					    GNUTLS_OID_X520_COMMON_NAME,
+					    0, /* First CN found, please */
+					    0, /* Not in raw mode */
+					    cn, &cn_size);
+	if (ret != 0) {
+		purple_debug_error("gnutls/x509",
+				   "Failed to get Common Name\n");
+		g_free(cn);
+		return NULL;
+	}
+
+	return cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	gnutls_x509_crt_t crt_dat;
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+	g_return_val_if_fail(name, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (gnutls_x509_crt_check_hostname(crt_dat, name)) {
+		return TRUE;
+	} else {
+		return FALSE;
+	}
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	gnutls_x509_crt_t crt_dat;
+	/* GnuTLS time functions return this on error */
+	const time_t errval = (time_t) (-1);
+
+
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_gnutls, FALSE);
+
+	crt_dat = X509_GET_GNUTLS_DATA(crt);
+
+	if (activation) {
+		*activation = gnutls_x509_crt_get_activation_time(crt_dat);
+	}
+	if (expiration) {
+		*expiration = gnutls_x509_crt_get_expiration_time(crt_dat);
+	}
+
+	if (*activation == errval || *expiration == errval) {
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+/* X.509 certificate operations provided by this plugin */
+static PurpleCertificateScheme x509_gnutls = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	x509_certificate_signed_by,      /* Signature checker */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	x509_cert_dn,                    /* Unique ID */
+	x509_issuer_dn,                  /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times,                      /* Activation/Expiration time */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_gnutls_init,
@@ -221,11 +928,11 @@
 	ssl_gnutls_close,
 	ssl_gnutls_read,
 	ssl_gnutls_write,
+	ssl_gnutls_get_peer_certificates,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -242,6 +949,9 @@
 	/* Init GNUTLS now so others can use it even if sslconn never does */
 	ssl_gnutls_init_gnutls();
 
+	/* Register that we're providing an X.509 CertScheme */
+	purple_certificate_register_scheme( &x509_gnutls );
+
 	return TRUE;
 #else
 	return FALSE;
@@ -255,6 +965,8 @@
 	if(purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	purple_certificate_unregister_scheme( &x509_gnutls );
 #endif
 
 	return TRUE;
--- a/libpurple/plugins/ssl/ssl-nss.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,10 +17,11 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "debug.h"
+#include "certificate.h"
 #include "plugin.h"
 #include "sslconn.h"
 #include "version.h"
@@ -106,6 +107,20 @@
 	}
 }
 
+static gchar *get_error_text()
+{
+	PRInt32 len = PR_GetErrorTextLength();
+	gchar *ret = NULL;
+
+	if (len > 0) {
+		ret = g_malloc(len + 1);
+		len = PR_GetErrorText(ret);
+		ret[len] = '\0';
+	}
+
+	return ret;
+}
+
 static void
 ssl_nss_init_nss(void)
 {
@@ -219,11 +234,14 @@
 	 * It seems to work because it'll eventually use the cached value
 	 */
 	if(SSL_ForceHandshake(nss_data->in) != SECSuccess) {
+		gchar *error_txt;
 		set_errno(PR_GetError());
 		if (errno == EAGAIN || errno == EWOULDBLOCK)
 			return;
 
-		purple_debug_error("nss", "Handshake failed %d\n", PR_GetError());
+		error_txt = get_error_text();
+		purple_debug_error("nss", "Handshake failed %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
+		g_free(error_txt);
 
 		if (gsc->error_cb != NULL)
 			gsc->error_cb(gsc, PURPLE_SSL_HANDSHAKE_FAILED, gsc->connect_cb_data);
@@ -264,8 +282,11 @@
 	socket_opt.option = PR_SockOpt_Nonblocking;
 	socket_opt.value.non_blocking = PR_TRUE;
 
-	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS)
-		purple_debug_warning("nss", "unable to set socket into non-blocking mode: %d\n", PR_GetError());
+	if (PR_SetSocketOption(nss_data->fd, &socket_opt) != PR_SUCCESS) {
+		gchar *error_txt = get_error_text();
+		purple_debug_warning("nss", "unable to set socket into non-blocking mode: %s (%d)\n", error_txt ? error_txt : "", PR_GetError());
+		g_free(error_txt);
+	}
 
 	nss_data->in = SSL_ImportFD(NULL, nss_data->fd);
 
@@ -360,6 +381,298 @@
 	return ret;
 }
 
+static GList *
+ssl_nss_peer_certs(PurpleSslConnection *gsc)
+{
+	PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc);
+	CERTCertificate *cert;
+/*
+	GList *chain = NULL;
+	void *pinArg;
+	SECStatus status;
+*/
+
+	/* TODO: this is a blind guess */
+	cert = SSL_PeerCertificate(nss_data->fd);
+
+	
+
+	return NULL;
+}
+
+/************************************************************************/
+/* X.509 functionality                                                  */
+/************************************************************************/
+static PurpleCertificateScheme x509_nss;
+
+/** Helpr macro to retrieve the NSS certdata from a PurpleCertificate */
+#define X509_NSS_DATA(pcrt) ( (CERTCertificate * ) (pcrt->data) )
+
+/** Imports a PEM-formatted X.509 certificate from the specified file.
+ * @param filename Filename to import from. Format is PEM
+ *
+ * @return A newly allocated Certificate structure of the x509_gnutls scheme
+ */
+static PurpleCertificate *
+x509_import_from_file(const gchar *filename)
+{
+	gchar *rawcert;
+	gsize len = 0;
+	CERTCertificate *crt_dat;
+	PurpleCertificate *crt;
+
+	g_return_val_if_fail(filename, NULL);
+
+	purple_debug_info("nss/x509",
+			  "Loading certificate from %s\n",
+			  filename);
+	
+	/* Load the raw data up */
+	g_return_val_if_fail(
+		g_file_get_contents(filename,
+				    &rawcert, &len,
+				    NULL ),
+		NULL);
+
+	/* Decode the certificate */
+	crt_dat = CERT_DecodeCertFromPackage(rawcert, len);
+	g_free(rawcert);
+
+	g_return_val_if_fail(crt_dat, NULL);
+	
+	crt = g_new0(PurpleCertificate, 1);
+	crt->scheme = &x509_nss;
+	crt->data = crt_dat;
+	
+	return crt;
+}
+
+/**
+ * Exports a PEM-formatted X.509 certificate to the specified file.
+ * @param filename Filename to export to. Format will be PEM
+ * @param crt      Certificate to export
+ *
+ * @return TRUE if success, otherwise FALSE
+ */
+static gboolean
+x509_export_certificate(const gchar *filename, PurpleCertificate *crt)
+{
+	/* TODO: WRITEME */
+	return FALSE;
+}
+
+static PurpleCertificate *
+x509_copy_certificate(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	PurpleCertificate *newcrt;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Create the certificate copy */
+	newcrt = g_new0(PurpleCertificate, 1);
+	newcrt->scheme = &x509_nss;
+	/* NSS does refcounting automatically */
+	newcrt->data = CERT_DupCertificate(crt_dat);
+	
+	return newcrt;
+}
+
+/** Frees a Certificate
+ *
+ *  Destroys a Certificate's internal data structures and frees the pointer
+ *  given.
+ *  @param crt  Certificate instance to be destroyed. It WILL NOT be destroyed
+ *              if it is not of the correct CertificateScheme. Can be NULL
+ *
+ */
+static void
+x509_destroy_certificate(PurpleCertificate * crt)
+{
+	CERTCertificate *crt_dat;
+
+	g_return_if_fail(crt);
+	g_return_if_fail(crt->scheme == &x509_nss);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_if_fail(crt_dat);
+
+	/* Finally we have the certificate. So let's kill it */
+	/* NSS does refcounting automatically */
+	CERT_DestroyCertificate(crt_dat);
+
+	/* Delete the PurpleCertificate as well */
+	g_free(crt);
+}
+
+#if 0
+/** Determines whether one certificate has been issued and signed by another
+ *
+ * @param crt       Certificate to check the signature of
+ * @param issuer    Issuer's certificate
+ *
+ * @return TRUE if crt was signed and issued by issuer, otherwise FALSE
+ * @TODO  Modify this function to return a reason for invalidity?
+ */
+static gboolean
+x509_certificate_signed_by(PurpleCertificate * crt,
+			   PurpleCertificate * issuer)
+{
+	return FALSE;
+}
+#endif
+
+static GByteArray *
+x509_sha1sum(PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	size_t hashlen = 20; /* Size of an sha1sum */
+	GByteArray *sha1sum;
+	SECItem *derCert; /* DER representation of the cert */
+	SECStatus st;
+
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Get the certificate DER representation */
+	derCert = &(crt_dat->derCert);
+
+	/* Make a hash! */
+	sha1sum = g_byte_array_sized_new(hashlen);
+	/* glib leaves the size as 0 by default */
+	sha1sum->len = hashlen;
+	
+	st = PK11_HashBuf(SEC_OID_SHA1, sha1sum->data,
+			  derCert->data, derCert->len);
+
+	/* Check for errors */
+	if (st != SECSuccess) {
+		g_byte_array_free(sha1sum, TRUE);
+		purple_debug_error("nss/x509",
+				   "Error: hashing failed!\n");
+		return NULL;
+	}
+
+	return sha1sum;
+}
+
+static gchar *
+x509_common_name (PurpleCertificate *crt)
+{
+	CERTCertificate *crt_dat;
+	char *nss_cn;
+	gchar *ret_cn;
+	
+	g_return_val_if_fail(crt, NULL);
+	g_return_val_if_fail(crt->scheme == &x509_nss, NULL);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, NULL);
+
+	/* Q:
+	   Why get a newly allocated string out of NSS, strdup it, and then
+	   return the new copy?
+
+	   A:
+	   The NSS LXR docs state that I should use the NSPR free functions on
+	   the strings that the NSS cert functions return. Since the libpurple
+	   API expects a g_free()-able string, we make our own copy and return
+	   that.
+
+	   NSPR is something of a prima donna. */
+
+	nss_cn = CERT_GetCommonName( &(crt_dat->subject) );
+	ret_cn = g_strdup(nss_cn);
+	PORT_Free(nss_cn);
+
+	return ret_cn;
+}
+
+static gboolean
+x509_check_name (PurpleCertificate *crt, const gchar *name)
+{
+	CERTCertificate *crt_dat;
+	SECStatus st;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	st = CERT_VerifyCertName(crt_dat, name);
+
+	if (st == SECSuccess) {
+		return TRUE;
+	}
+	else if (st == SECFailure) {
+		return FALSE;
+	}
+	
+	/* If we get here...bad things! */
+	purple_debug_error("nss/x509",
+			   "x509_check_name fell through where it shouldn't "
+			   "have.\n");
+	return FALSE;
+}
+
+static gboolean
+x509_times (PurpleCertificate *crt, time_t *activation, time_t *expiration)
+{
+	CERTCertificate *crt_dat;
+	PRTime nss_activ, nss_expir;
+	
+	g_return_val_if_fail(crt, FALSE);
+	g_return_val_if_fail(crt->scheme == &x509_nss, FALSE);
+
+	crt_dat = X509_NSS_DATA(crt);
+	g_return_val_if_fail(crt_dat, FALSE);
+
+	/* Extract the times into ugly PRTime thingies */
+	/* TODO: Maybe this shouldn't throw an error? */
+	g_return_val_if_fail(
+		SECSuccess == CERT_GetCertTimes(crt_dat,
+						&nss_activ, &nss_expir),
+		FALSE);
+
+	if (activation) {
+		*activation = nss_activ;
+	}
+	if (expiration) {
+		*expiration = nss_expir;
+	}
+	
+	return TRUE;
+}
+
+static PurpleCertificateScheme x509_nss = {
+	"x509",                          /* Scheme name */
+	N_("X.509 Certificates"),        /* User-visible scheme name */
+	x509_import_from_file,           /* Certificate import function */
+	x509_export_certificate,         /* Certificate export function */
+	x509_copy_certificate,           /* Copy */
+	x509_destroy_certificate,        /* Destroy cert */
+	NULL,                            /* Signed-by */
+	x509_sha1sum,                    /* SHA1 fingerprint */
+	NULL,                            /* Unique ID */
+	NULL,                            /* Issuer Unique ID */
+	x509_common_name,                /* Subject name */
+	x509_check_name,                 /* Check subject name */
+	x509_times,                      /* Activation/Expiration time */
+
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
 static PurpleSslOps ssl_ops =
 {
 	ssl_nss_init,
@@ -368,11 +681,11 @@
 	ssl_nss_close,
 	ssl_nss_read,
 	ssl_nss_write,
+	ssl_nss_peer_certs,
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -390,6 +703,9 @@
 	/* Init NSS now, so others can use it even if sslconn never does */
 	ssl_nss_init_nss();
 
+	/* Register the X.509 functions we provide */
+	purple_certificate_register_scheme(&x509_nss);
+
 	return TRUE;
 #else
 	return FALSE;
@@ -403,6 +719,9 @@
 	if (purple_ssl_get_ops() == &ssl_ops) {
 		purple_ssl_set_ops(NULL);
 	}
+
+	/* Unregister our X.509 functions */
+	purple_certificate_unregister_scheme(&x509_nss);
 #endif
 
 	return TRUE;
--- a/libpurple/plugins/ssl/ssl.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/ssl/ssl.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "debug.h"
--- a/libpurple/plugins/startup.py	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/startup.py	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
 #
 # 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 #
 
 import sys
--- a/libpurple/plugins/tcl/tcl.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "config.h"
--- a/libpurple/plugins/tcl/tcl_cmd.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_cmd.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <tcl.h>
 
--- a/libpurple/plugins/tcl/tcl_cmds.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_cmds.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <tcl.h>
@@ -544,12 +544,16 @@
 
 int tcl_cmd_cmd(ClientData unused, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
 {
-	const char *cmds[] = { "register", "unregister", NULL };
-	enum { CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd;
+	const char *cmds[] = { "do", "help", "list", "register", "unregister", NULL };
+	enum { CMD_CMD_DO, CMD_CMD_HELP, CMD_CMD_LIST, CMD_CMD_REGISTER, CMD_CMD_UNREGISTER } cmd;
 	struct tcl_cmd_handler *handler;
-	Tcl_Obj *result = Tcl_GetObjResult(interp);
+	Tcl_Obj *list, *elem, *result = Tcl_GetObjResult(interp);
+	PurpleConversation *convo;
 	PurpleCmdId id;
+	PurpleCmdStatus status;
 	int error;
+	GList *l, *cur;
+	gchar *escaped, *errstr = NULL;
 
 	if (objc < 2) {
 		Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?args?");
@@ -560,6 +564,57 @@
 		return error;
 
 	switch (cmd) {
+	case CMD_CMD_DO:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation command");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		escaped = g_markup_escape_text(Tcl_GetString(objv[3]), -1);
+		status = purple_cmd_do_command(convo, Tcl_GetString(objv[3]),
+				escaped, &errstr);
+		g_free(escaped);
+		Tcl_SetStringObj(result, errstr ? (char *)errstr : "", -1);
+		g_free(errstr);
+		if (status != PURPLE_CMD_STATUS_OK) {
+			return TCL_ERROR;
+		}
+		break;
+	case CMD_CMD_HELP:
+		if (objc != 4) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation name");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		l = cur = purple_cmd_help(convo, Tcl_GetString(objv[3]));
+		list = Tcl_NewListObj(0, NULL);
+		while (cur != NULL) {
+			elem = Tcl_NewStringObj((char *)cur->data, -1);
+			Tcl_ListObjAppendElement(interp, list, elem);
+			cur = g_list_next(cur);
+		}
+		g_list_free(l);
+		Tcl_SetObjResult(interp, list);
+		break;
+	case CMD_CMD_LIST:
+		if (objc != 3) {
+			Tcl_WrongNumArgs(interp, 2, objv, "conversation");
+			return TCL_ERROR;
+		}
+		if ((convo = tcl_validate_conversation(objv[2], interp)) == NULL)
+			return TCL_ERROR;
+		l = cur = purple_cmd_list(convo);
+		list = Tcl_NewListObj(0, NULL);
+		while (cur != NULL) {
+			elem = Tcl_NewStringObj((char *)cur->data, -1);
+			Tcl_ListObjAppendElement(interp, list, elem);
+			cur = g_list_next(cur);
+		}
+		g_list_free(l);
+		Tcl_SetObjResult(interp, list);
+		break;
 	case CMD_CMD_REGISTER:
 		if (objc != 9) {
 			Tcl_WrongNumArgs(interp, 2, objv, "cmd arglist priority flags prpl_id proc helpstr");
--- a/libpurple/plugins/tcl/tcl_glib.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_glib.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  * TERMS 2
  *
--- a/libpurple/plugins/tcl/tcl_glib.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_glib.h	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_TCL_GLIB_H_
--- a/libpurple/plugins/tcl/tcl_purple.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_purple.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_TCL_PURPLE_H_
--- a/libpurple/plugins/tcl/tcl_ref.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_ref.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include <tcl.h>
--- a/libpurple/plugins/tcl/tcl_signals.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_signals.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <tcl.h>
 #include <stdarg.h>
--- a/libpurple/pounce.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/pounce.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 #include "conversation.h"
--- a/libpurple/pounce.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/pounce.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_POUNCE_H_
 #define _PURPLE_POUNCE_H_
--- a/libpurple/prefs.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/prefs.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -329,9 +329,13 @@
 				purple_prefs_set_string_list(pref_name_full->str, NULL);
 				break;
 			case PURPLE_PREF_PATH:
-				decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
-				purple_prefs_set_path(pref_name_full->str, decoded);
-				g_free(decoded);
+				if (pref_value) {
+					decoded = g_filename_from_utf8(pref_value, -1, NULL, NULL, NULL);
+					purple_prefs_set_path(pref_name_full->str, decoded);
+					g_free(decoded);
+				} else {
+					purple_prefs_set_path(pref_name_full->str, NULL);
+				}
 				break;
 			case PURPLE_PREF_PATH_LIST:
 				purple_prefs_set_path_list(pref_name_full->str, NULL);
@@ -1446,6 +1450,9 @@
 	purple_prefs_remove("/purple/contact/offline_score");
 	purple_prefs_remove("/purple/contact/away_score");
 	purple_prefs_remove("/purple/contact/idle_score");
+
+	purple_prefs_load();
+	purple_prefs_update_old();
 }
 
 void
--- a/libpurple/prefs.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/prefs.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #ifndef _PURPLE_PREFS_H_
@@ -55,7 +55,9 @@
 #endif
 
 /**************************************************************************/
-/** @name Prefs API                                                       */
+/** @name Prefs API                                                       
+    Preferences are named according to a directory-like structure.        
+    Example: "/plugins/core/potato/is_from_idaho" (probably a boolean)    */
 /**************************************************************************/
 /*@{*/
 
--- a/libpurple/privacy.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/privacy.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "internal.h"
 
--- a/libpurple/privacy.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/privacy.h	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_PRIVACY_H_
 #define _PURPLE_PRIVACY_H_
--- a/libpurple/protocols/Makefile.am	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -1,5 +1,5 @@
 EXTRA_DIST = Makefile.mingw
 
-DIST_SUBDIRS = bonjour gg irc jabber msn novell null oscar qq sametime silc silc10 toc simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn myspace novell null oscar qq sametime silc silc10 toc simple yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/Makefile.mingw	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -8,7 +8,7 @@
 PIDGIN_TREE_TOP := ../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
-SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc10 simple yahoo bonjour
+SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc simple yahoo bonjour myspace
 
 .PHONY: all install clean
 
--- a/libpurple/protocols/bonjour/bonjour.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include <glib.h>
 #ifndef _WIN32
@@ -235,6 +235,13 @@
 	g_free(stripped);
 }
 
+static void bonjour_remove_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) {
+	if (buddy->proto_data) {
+		bonjour_buddy_delete(buddy->proto_data);
+		buddy->proto_data = NULL;
+	}
+}
+
 static GList *
 bonjour_status_types(PurpleAccount *account)
 {
@@ -395,7 +402,7 @@
 	NULL,                                                    /* change_passwd */
 	NULL,                                                    /* add_buddy */
 	NULL,                                                    /* add_buddies */
-	NULL,                                                    /* remove_buddy */
+	bonjour_remove_buddy,                                    /* remove_buddy */
 	NULL,                                                    /* remove_buddies */
 	NULL,                                                    /* add_permit */
 	NULL,                                                    /* add_deny */
@@ -479,45 +486,50 @@
 	NULL
 };
 
-static void
-initialize_default_account_values()
-{
-#ifdef _WIN32
-	char *fullname = NULL;
-#else
-	struct passwd *info;
-	const char *fullname = NULL;
-#endif
-	char *splitpoint = NULL;
-	char *tmp;
-	char hostname[255];
+#ifdef WIN32
+static gboolean _set_default_name_cb(gpointer data) {
+	gchar *fullname = data;
+	const char *splitpoint;
+	GList *tmp = prpl_info.protocol_options;
+	PurpleAccountOption *option;
 
-#ifndef _WIN32
-	/* Try to figure out the user's real name */
-	info = getpwuid(getuid());
-	if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0'))
-		fullname = info->pw_gecos;
-	else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0'))
-		fullname = info->pw_name;
-	else if (((fullname = getlogin()) != NULL) && (fullname[0] != '\0'))
-		;
-	else
-		fullname = _("Purple Person");
-	/* Make sure fullname is valid UTF-8.  If not, try to convert it. */
-	if (!g_utf8_validate(fullname, -1, NULL))
-	{
-		gchar *tmp;
-		tmp = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL);
-		if ((tmp == NULL) || (*tmp == '\0'))
-			fullname = _("Purple Person");
+	if (!fullname) {
+		purple_debug_info("bonjour", "Unable to look up First and Last name or Username from system; using defaults.\n");
+		return FALSE;
 	}
 
-#else
+	g_free(default_firstname);
+	g_free(default_lastname);
+
+	/* Split the real name into a first and last name */
+	splitpoint = strchr(fullname, ' ');
+	if (splitpoint != NULL) {
+		default_firstname = g_strndup(fullname, splitpoint - fullname);
+		default_lastname = g_strdup(&splitpoint[1]);
+	} else {
+		default_firstname = g_strdup(fullname);
+		default_lastname = g_strdup("");
+	}
+	g_free(fullname);
+
+
+	for(; tmp != NULL; tmp = tmp->next) {
+		option = tmp->data;
+		if (strcmp("first", purple_account_option_get_setting(option)) == 0)
+			purple_account_option_set_default_string(option, default_firstname);
+		else if (strcmp("last", purple_account_option_get_setting(option)) == 0)
+			purple_account_option_set_default_string(option, default_lastname);
+	}
+
+	return FALSE;
+}
+
+static gpointer _win32_name_lookup_thread(gpointer data) {
+	gchar *fullname = NULL;
 	wchar_t username[UNLEN + 1];
 	DWORD dwLenUsername = UNLEN + 1;
 
-	if (!GetUserNameW((LPWSTR) &username, &dwLenUsername))
-		purple_debug_warning("bonjour", "Unable to look up username\n");
+	GetUserNameW((LPWSTR) &username, &dwLenUsername);
 
 	if (username != NULL && *username != '\0') {
 		LPBYTE servername = NULL;
@@ -525,7 +537,7 @@
 
 		NetGetDCName(NULL, NULL, &servername);
 
-		purple_debug_info("bonjour", "Looking up the full name from the %s.\n", (servername ? "domain controller" : "local machine"));
+		/* purple_debug_info("bonjour", "Looking up the full name from the %s.\n", (servername ? "domain controller" : "local machine")); */
 
 		if (NetUserGetInfo((LPCWSTR) servername, username, 10, &info) == NERR_Success
 				&& info != NULL && ((LPUSER_INFO_10) info)->usri10_full_name != NULL
@@ -536,7 +548,7 @@
 		}
 		/* Fall back to the local machine if we didn't get the full name from the domain controller */
 		else if (servername != NULL) {
-			purple_debug_info("bonjour", "Looking up the full name from the local machine");
+			/* purple_debug_info("bonjour", "Looking up the full name from the local machine"); */
 
 			if (info != NULL) NetApiBufferFree(info);
 			info = NULL;
@@ -552,20 +564,54 @@
 
 		if (info != NULL) NetApiBufferFree(info);
 		if (servername != NULL) NetApiBufferFree(servername);
+
+		if (!fullname)
+			fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL);
 	}
 
-	if (!fullname) {
-		if (username != NULL && *username != '\0')
-			fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL);
-		else
-			fullname = g_strdup(_("Purple Person"));
+	g_idle_add(_set_default_name_cb, fullname);
+
+	return NULL;
+}
+#endif
+
+static void
+initialize_default_account_values()
+{
+#ifndef _WIN32
+	struct passwd *info;
+#endif
+	const char *fullname = NULL, *splitpoint, *tmp;
+	char hostname[255];
+	gchar *conv = NULL;
+
+#ifndef _WIN32
+	/* Try to figure out the user's real name */
+	info = getpwuid(getuid());
+	if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0'))
+		fullname = info->pw_gecos;
+	else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0'))
+		fullname = info->pw_name;
+	else if (((fullname = getlogin()) != NULL) && (fullname[0] == '\0'))
+		fullname = NULL;
+#else
+	/* The Win32 username lookup functions are synchronous so we do it in a thread */
+	g_thread_create(_win32_name_lookup_thread, NULL, FALSE, NULL);
+#endif
+
+	/* Make sure fullname is valid UTF-8.  If not, try to convert it. */
+	if (fullname != NULL && !g_utf8_validate(fullname, -1, NULL)) {
+		fullname = conv = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL);
+		if (conv == NULL || *conv == '\0')
+			fullname = NULL;
 	}
-#endif
+
+	if (fullname == NULL)
+		fullname = _("Purple Person");
 
 	/* Split the real name into a first and last name */
 	splitpoint = strchr(fullname, ' ');
-	if (splitpoint != NULL)
-	{
+	if (splitpoint != NULL) {
 		default_firstname = g_strndup(fullname, splitpoint - fullname);
 		tmp = &splitpoint[1];
 
@@ -577,16 +623,12 @@
 			default_lastname = g_strndup(tmp, splitpoint - tmp);
 		else
 			default_lastname = g_strdup(tmp);
-	}
-	else
-	{
+	} else {
 		default_firstname = g_strdup(fullname);
 		default_lastname = g_strdup("");
 	}
 
-#ifdef _WIN32
-	g_free(fullname);
-#endif
+	g_free(conv);
 
 	/* Try to figure out a good host name to use */
 	/* TODO: Avoid 'localhost,' if possible */
--- a/libpurple/protocols/bonjour/bonjour.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/protocols/bonjour/buddy.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #include <glib.h>
@@ -121,9 +121,8 @@
  * the buddy.
  */
 void
-bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy)
+bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *buddy)
 {
-	PurpleBuddy *buddy;
 	PurpleGroup *group;
 	PurpleAccount *account = bonjour_buddy->account;
 	const char *status_id, *old_hash, *new_hash;
@@ -147,7 +146,8 @@
 	}
 
 	/* Make sure the buddy exists in our buddy list */
-	buddy = purple_find_buddy(account, bonjour_buddy->name);
+	if (buddy == NULL)
+		buddy = purple_find_buddy(account, bonjour_buddy->name);
 
 	if (buddy == NULL) {
 		buddy = purple_buddy_new(account, bonjour_buddy->name, NULL);
--- a/libpurple/protocols/bonjour/buddy.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #ifndef _BONJOUR_BUDDY
@@ -92,8 +92,9 @@
 
 /**
  * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???)
+ * purple_buddy is optional; it saves an additional lookup if we already have it
  */
-void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
+void bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy, PurpleBuddy *purple_buddy);
 
 /**
  * We got the buddy icon data; deal with it
--- a/libpurple/protocols/bonjour/issues.txt	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Wed Sep 12 19:11:38 2007 +0000
@@ -2,6 +2,5 @@
 ============= Known issues ===============
 ==========================================
 
-* Status changes don't work
 * File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/jabber.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _WIN32
 #include <sys/socket.h>
@@ -82,8 +82,8 @@
 	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
 	bconv->socket = -1;
 	bconv->tx_buf = purple_circ_buffer_new(512);
-	bconv->tx_handler = -1;
-	bconv->rx_handler = -1;
+	bconv->tx_handler = 0;
+	bconv->rx_handler = 0;
 
 	return bconv;
 }
@@ -234,7 +234,7 @@
 
 	if (writelen == 0) {
 		purple_input_remove(bconv->tx_handler);
-		bconv->tx_handler = -1;
+		bconv->tx_handler = 0;
 		return;
 	}
 
@@ -272,7 +272,7 @@
 	BonjourJabberConversation *bconv = bb->conversation;
 
 	/* If we're not ready to actually send, append it to the buffer */
-	if (bconv->tx_handler != -1
+	if (bconv->tx_handler != 0
 			|| bconv->connect_data != NULL
 			|| !bconv->sent_stream_start
 			|| !bconv->recv_stream_start
@@ -304,7 +304,7 @@
 	}
 
 	if (ret < len) {
-		if (bconv->tx_handler == -1)
+		if (bconv->tx_handler == 0)
 			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
 				_send_data_write_cb, pb);
 		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
@@ -373,17 +373,19 @@
 
 	g_return_if_fail(bb != NULL);
 
-	/* Close the socket, clear the watcher and free memory */
-	bonjour_jabber_close_conversation(bb->conversation);
-	bb->conversation = NULL;
+	/* Inform the user that the conversation has been closed */
+	if (bb->conversation != NULL) {
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
+		if (conv != NULL) {
+			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+		}
+		/* Close the socket, clear the watcher and free memory */
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+	}
 
-	/* Inform the user that the conversation has been closed */
-	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->account);
-	if (conv != NULL) {
-		char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
-		purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
-		g_free(tmp);
-	}
 }
 
 void bonjour_jabber_stream_started(PurpleBuddy *pb) {
@@ -455,7 +457,7 @@
 
 	/* Stream started; process the send buffer if there is one */
 	purple_input_remove(bconv->tx_handler);
-	bconv->tx_handler= -1;
+	bconv->tx_handler= 0;
 	bconv->sent_stream_start = TRUE;
 
 	bonjour_jabber_stream_started(pb);
@@ -535,7 +537,7 @@
 
 	/* Look for the buddy that has opened the conversation and fill information */
 	address_text = inet_ntoa(their_addr.sin_addr);
-	purple_debug_info("bonjour", "Received incoming connection from %s\n.", address_text);
+	purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
 	cbba = g_new0(struct _check_buddy_by_address_t, 1);
 	cbba->address = address_text;
 	cbba->pb = &pb;
@@ -779,7 +781,7 @@
 			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
 			close(bconv->socket);
 		}
-		if (bconv->rx_handler != -1)
+		if (bconv->rx_handler != 0)
 			purple_input_remove(bconv->rx_handler);
 		if (bconv->tx_handler > 0)
 			purple_input_remove(bconv->tx_handler);
--- a/libpurple/protocols/bonjour/jabber.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #include "internal.h"
@@ -33,7 +33,7 @@
 #include <avahi-glib/glib-malloc.h>
 #include <avahi-glib/glib-watch.h>
 
-/* For some reason, this is missing from the Avahi type defines */
+/* Avahi only defines the types that it actually uses (which at this time doesn't include NULL) */
 #ifndef AVAHI_DNS_TYPE_NULL
 #define AVAHI_DNS_TYPE_NULL 0x0A
 #endif
@@ -58,7 +58,8 @@
 		  const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
 		  AvahiLookupResultFlags flags, void *userdata) {
 
-	BonjourBuddy *buddy;
+	PurpleBuddy *pb;
+	BonjourBuddy *bb;
 	PurpleAccount *account = userdata;
 	AvahiStringList *l;
 	size_t size;
@@ -67,44 +68,62 @@
 
 	g_return_if_fail(r != NULL);
 
+	pb = purple_find_buddy(account, name);
+	bb = (pb != NULL) ? pb->proto_data : NULL;
+
 	switch (event) {
 		case AVAHI_RESOLVER_FAILURE:
 			purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
 				avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+
 			avahi_service_resolver_free(r);
+			if (bb != NULL) {
+				/* We've already freed the resolver */
+				if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver)
+					((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL;
+				purple_blist_remove_buddy(pb);
+			}
 			break;
 		case AVAHI_RESOLVER_FOUND:
 			/* create a buddy record */
-			buddy = bonjour_buddy_new(name, account);
+			if (bb == NULL)
+				bb = bonjour_buddy_new(name, account);
 
-			((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r;
+			/* If we're reusing an existing buddy, make sure if it is a different resolver to clean up the old one.
+			 * I don't think this should ever happen, but I'm afraid we might get events out of sequence. */
+			if (((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != NULL
+					&& ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != r) {
+				avahi_service_resolver_free(((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver);
+			}
+			((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = r;
 
+			g_free(bb->ip);
 			/* Get the ip as a string */
-			buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
-			avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
+			bb->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
+			avahi_address_snprint(bb->ip, AVAHI_ADDRESS_STR_MAX, a);
 
-			buddy->port_p2pj = port;
+			bb->port_p2pj = port;
 
 			/* Obtain the parameters from the text_record */
-			clear_bonjour_buddy_values(buddy);
-			l = txt;
-			while (l != NULL) {
-				ret = avahi_string_list_get_pair(l, &key, &value, &size);
-				l = l->next;
-				if (ret < 0)
+			clear_bonjour_buddy_values(bb);
+			for(l = txt; l != NULL; l = l->next) {
+				if ((ret = avahi_string_list_get_pair(l, &key, &value, &size)) < 0)
 					continue;
-				set_bonjour_buddy_value(buddy, key, value, size);
+				set_bonjour_buddy_value(bb, key, value, size);
 				/* TODO: Since we're using the glib allocator, I think we
 				 * can use the values instead of re-copying them */
 				avahi_free(key);
 				avahi_free(value);
 			}
 
-			if (!bonjour_buddy_check(buddy))
-				bonjour_buddy_delete(buddy);
-			else
+			if (!bonjour_buddy_check(bb)) {
+				if (pb != NULL)
+					purple_blist_remove_buddy(pb);
+				else
+					bonjour_buddy_delete(bb);
+			} else
 				/* Add or update the buddy in our buddy list */
-				bonjour_buddy_add_to_purple(buddy);
+				bonjour_buddy_add_to_purple(bb, pb);
 
 			break;
 		default:
@@ -120,7 +139,7 @@
 		  AvahiLookupResultFlags flags, void *userdata) {
 
 	PurpleAccount *account = userdata;
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 
 	switch (event) {
 		case AVAHI_BROWSER_FAILURE:
@@ -132,7 +151,7 @@
 			/* A new peer has joined the network and uses iChat bonjour */
 			purple_debug_info("bonjour", "_browser_callback - new service\n");
 			/* Make sure it isn't us */
-			if (g_ascii_strcasecmp(name, account->username) != 0) {
+			if (purple_utf8_strcasecmp(name, account->username) != 0) {
 				if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
 						interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC,
 						0, _resolver_callback, account)) {
@@ -143,16 +162,12 @@
 			break;
 		case AVAHI_BROWSER_REMOVE:
 			purple_debug_info("bonjour", "_browser_callback - Remove service\n");
-			gb = purple_find_buddy(account, name);
-			if (gb != NULL) {
-				bonjour_buddy_delete(gb->proto_data);
-				purple_blist_remove_buddy(gb);
-			}
+			pb = purple_find_buddy(account, name);
+			if (pb != NULL)
+				purple_blist_remove_buddy(pb);
 			break;
 		case AVAHI_BROWSER_ALL_FOR_NOW:
 		case AVAHI_BROWSER_CACHE_EXHAUSTED:
-			purple_debug_warning("bonjour", "(Browser) %s\n",
-				event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
 			break;
 		default:
 			purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event);
@@ -173,7 +188,7 @@
 			purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
 			break;
 		case AVAHI_ENTRY_GROUP_FAILURE:
-			purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.",
+			purple_debug_error("bonjour", "Error registering buddy icon data: %s.\n",
 				avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
 			break;
 		case AVAHI_ENTRY_GROUP_UNCOMMITED:
@@ -256,7 +271,7 @@
 	idata->client = avahi_client_new(poll_api, 0, NULL, data, &error);
 
 	if (idata->client == NULL) {
-		purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error));
+		purple_debug_error("bonjour", "Error initializing Avahi: %s\n", avahi_strerror(error));
 		avahi_glib_poll_free(idata->glib_poll);
 		g_free(idata);
 		return FALSE;
@@ -339,7 +354,7 @@
 	if (!idata->sb) {
 
 		purple_debug_error("bonjour",
-			"Unable to initialize service browser.  Error: %s\n.",
+			"Unable to initialize service browser.  Error: %s.\n",
 			avahi_strerror(avahi_client_errno(idata->client)));
 		return FALSE;
 	}
@@ -473,7 +488,7 @@
 
 	if (!idata->buddy_icon_rec_browser) {
 		purple_debug_error("bonjour",
-			"Unable to initialize buddy icon record browser.  Error: %s\n.",
+			"Unable to initialize buddy icon record browser.  Error: %s.\n",
 			avahi_strerror(avahi_client_errno(session_idata->client)));
 	}
 
--- a/libpurple/protocols/bonjour/mdns_common.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #include <string.h>
@@ -207,7 +207,7 @@
 
 	/* Advise the daemon that we are waiting for connections */
 	if (!_mdns_browse(data)) {
-		purple_debug_error("bonjour", "Unable to get service.");
+		purple_debug_error("bonjour", "Unable to get service.\n");
 		return FALSE;
 	}
 
--- a/libpurple/protocols/bonjour/mdns_common.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #ifndef _BONJOUR_MDNS_COMMON
--- a/libpurple/protocols/bonjour/mdns_howl.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #include "internal.h"
@@ -71,6 +71,7 @@
 	char value[SW_TEXT_RECORD_MAX_LEN];
 	sw_uint32 value_length;
 
+	/* TODO: We want to keep listening for updates*/
 	sw_discovery_cancel(discovery, oid);
 
 	/* create a buddy record */
@@ -100,7 +101,7 @@
 	}
 
 	/* Add or update the buddy in our buddy list */
-	bonjour_buddy_add_to_purple(buddy);
+	bonjour_buddy_add_to_purple(buddy, NULL);
 
 	return SW_OKAY;
 }
@@ -149,10 +150,7 @@
 			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
 			gb = purple_find_buddy(account, name);
 			if (gb != NULL)
-			{
-				bonjour_buddy_delete(gb->proto_data);
 				purple_blist_remove_buddy(gb);
-			}
 			break;
 		case SW_DISCOVERY_BROWSE_RESOLVED:
 			purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
--- a/libpurple/protocols/bonjour/mdns_interface.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_interface.h	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #ifndef _BONJOUR_MDNS_INTERFACE
--- a/libpurple/protocols/bonjour/mdns_types.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_types.h	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #ifndef _BONJOUR_MDNS_TYPES
--- a/libpurple/protocols/bonjour/mdns_win32.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Wed Sep 12 19:11:38 2007 +0000
@@ -11,7 +11,7 @@
  *
  *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 #include "internal.h"
@@ -79,27 +79,27 @@
 	uint32_t ttl, void *context)
 {
 
-	if (kDNSServiceErr_NoError != errorCode)
+	if (kDNSServiceErr_NoError != errorCode) {
 		purple_debug_error("bonjour", "record query - callback error.\n");
-	else if (flags & kDNSServiceFlagsAdd)
-	{
+		/* TODO: Probably should remove the buddy when this happens */
+	} else if (flags & kDNSServiceFlagsAdd) {
 		if (rrtype == kDNSServiceType_TXT) {
 			/* New Buddy */
-			BonjourBuddy *buddy = (BonjourBuddy*) context;
-			_mdns_parse_text_record(buddy, rdata, rdlen);
-			bonjour_buddy_add_to_purple(buddy);
+			BonjourBuddy *bb = (BonjourBuddy*) context;
+			_mdns_parse_text_record(bb, rdata, rdlen);
+			bonjour_buddy_add_to_purple(bb, NULL);
 		} else if (rrtype == kDNSServiceType_NULL) {
 			/* Buddy Icon response */
-			BonjourBuddy *buddy = (BonjourBuddy*) context;
-			Win32BuddyImplData *idata = buddy->mdns_impl_data;
+			BonjourBuddy *bb = (BonjourBuddy*) context;
+			Win32BuddyImplData *idata = bb->mdns_impl_data;
 
 			g_return_if_fail(idata != NULL);
 
-			bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen);
+			bonjour_buddy_got_buddy_icon(bb, rdata, rdlen);
 
 			/* We've got what we need; stop listening */
 			purple_input_remove(idata->null_query_handler);
-			idata->null_query_handler = -1;
+			idata->null_query_handler = 0;
 			DNSServiceRefDeallocate(idata->null_query);
 			idata->null_query = NULL;
 		}
@@ -110,32 +110,34 @@
 _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message)
 {
 	ResolveCallbackArgs* args = (ResolveCallbackArgs*)data;
+	BonjourBuddy* bb = args->buddy;
 
-	if (!hosts || !hosts->data)
+	if (!hosts || !hosts->data) {
 		purple_debug_error("bonjour", "host resolution - callback error.\n");
-	else {
+		bonjour_buddy_delete(bb);
+	} else {
 		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
-		BonjourBuddy* buddy = args->buddy;
-		Win32BuddyImplData *idata = buddy->mdns_impl_data;
+		Win32BuddyImplData *idata = bb->mdns_impl_data;
 
 		g_return_if_fail(idata != NULL);
 
-		buddy->ip = g_strdup(inet_ntoa(addr->sin_addr));
+		g_free(bb->ip);
+		bb->ip = g_strdup(inet_ntoa(addr->sin_addr));
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
 		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
 				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
-				kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+				kDNSServiceClass_IN, _mdns_record_query_callback, bb)) {
 
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj);
 
 			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
 				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
 
-			bonjour_buddy_add_to_purple(buddy);
+			bonjour_buddy_add_to_purple(bb, NULL);
 		} else
-			bonjour_buddy_delete(buddy);
+			bonjour_buddy_delete(bb);
 
 	}
 
@@ -202,18 +204,19 @@
     DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
 {
 	PurpleAccount *account = (PurpleAccount*)context;
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 
 	if (kDNSServiceErr_NoError != errorCode)
-		purple_debug_error("bonjour", "service browser - callback error");
+		purple_debug_error("bonjour", "service browser - callback error\n");
 	else if (flags & kDNSServiceFlagsAdd) {
 		/* A presence service instance has been discovered... check it isn't us! */
-		if (g_ascii_strcasecmp(serviceName, account->username) != 0) {
+		if (purple_utf8_strcasecmp(serviceName, account->username) != 0) {
 			/* OK, lets go ahead and resolve it to add to the buddy list */
 			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
 			args->buddy = bonjour_buddy_new(serviceName, account);
 
-			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) {
+			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype,
+					replyDomain, _mdns_service_resolve_callback, args)) {
 				bonjour_buddy_delete(args->buddy);
 				g_free(args);
 				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
@@ -226,11 +229,9 @@
 	} else {
 		/* A peer has sent a goodbye packet, remove them from the buddy list */
 		purple_debug_info("bonjour", "service browser - remove notification\n");
-		gb = purple_find_buddy(account, serviceName);
-		if (gb != NULL) {
-			bonjour_buddy_delete(gb->proto_data);
-			purple_blist_remove_buddy(gb);
-		}
+		pb = purple_find_buddy(account, serviceName);
+		if (pb != NULL)
+			purple_blist_remove_buddy(pb);
 	}
 }
 
--- a/libpurple/protocols/bonjour/parser.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
--- a/libpurple/protocols/bonjour/parser.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/bonjour/parser.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_BONJOUR_PARSER_H_
 #define _PURPLE_BONJOUR_PARSER_H_
--- a/libpurple/protocols/gg/buddylist.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/buddylist.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/buddylist.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/buddylist.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/confer.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/confer.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/confer.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/confer.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/gg-utils.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/gg-utils.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/gg-utils.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/gg-utils.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_GG_UTILS_H
--- a/libpurple/protocols/gg/gg.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/gg.c	Wed Sep 12 19:11:38 2007 +0000
@@ -22,7 +22,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
@@ -411,6 +411,8 @@
 	purple_notify_info(NULL, _("New Gadu-Gadu Account Registered"),
 			 _("Registration completed successfully!"), NULL);
 
+	if(account->registration_cb)
+		(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
 	/* TODO: the currently open Accounts Window will not be updated withthe
 	 * new username and etc, we need to somehow have it refresh at this
 	 * point
@@ -420,6 +422,9 @@
 	purple_connection_destroy(gc);
 
 exit_err:
+	if(account->registration_cb)
+		(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+
 	gg_register_free(h);
 	g_free(email);
 	g_free(p1);
--- a/libpurple/protocols/gg/gg.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/gg.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/lib/COPYING	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/COPYING	Wed Sep 12 19:11:38 2007 +0000
@@ -2,7 +2,7 @@
 		       Version 2.1, February 1999
 
  Copyright (C) 1991, 1999 Free Software Foundation, Inc.
-     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+     51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
@@ -485,7 +485,7 @@
 
     You should have received a copy of the GNU Lesser General Public
     License along with this library; if not, write to the Free Software
-    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 
 Also add information on how to contact you by electronic and paper mail.
 
--- a/libpurple/protocols/gg/lib/common.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/common.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/compat.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/compat.h	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/dcc.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/dcc.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/events.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/events.c	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/http.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/http.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,7 +14,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/libgadu.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/libgadu.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/obsolete.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/obsolete.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,7 +14,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/pubdir.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/pubdir.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/lib/pubdir50.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/lib/pubdir50.c	Wed Sep 12 19:11:38 2007 +0000
@@ -14,7 +14,7 @@
  *
  *  You should have received a copy of the GNU Lesser General Public
  *  License along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301,
  *  USA.
  */
 
--- a/libpurple/protocols/gg/search.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/search.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/gg/search.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/gg/search.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 
--- a/libpurple/protocols/irc/cmds.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/cmds.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/protocols/irc/dcc_send.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/dcc_send.c	Wed Sep 12 19:11:38 2007 +0000
@@ -18,7 +18,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/protocols/irc/irc.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Wed Sep 12 19:11:38 2007 +0000
@@ -20,7 +20,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
@@ -347,6 +347,7 @@
 	const char *username, *realname;
 	struct irc_conn *irc = gc->proto_data;
 	const char *pass = purple_connection_get_password(gc);
+	int ret;
 
 	if (pass && *pass) {
 		buf = irc_format(irc, "vv", "PASS", pass);
@@ -359,8 +360,12 @@
 	}
 
 
-	gethostname(hostname, sizeof(hostname));
+	ret = gethostname(hostname, sizeof(hostname));
 	hostname[sizeof(hostname) - 1] = '\0';
+	if (ret < 0 || hostname[0] == '\0') {
+		purple_debug_warning("irc", "gethostname() failed -- is your hostname set?");
+		strcpy(hostname, "localhost");
+	}
 	realname = purple_account_get_string(irc->account, "realname", "");
 	username = purple_account_get_string(irc->account, "username", "");
 
@@ -433,14 +438,7 @@
 
 	irc->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void irc_close(PurpleConnection *gc)
--- a/libpurple/protocols/irc/irc.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/irc.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_IRC_H
--- a/libpurple/protocols/irc/msgs.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/msgs.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/protocols/irc/parse.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/irc/parse.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/protocols/jabber/Makefile.am	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -27,6 +27,8 @@
 			  oob.h \
 			  parser.c \
 			  parser.h \
+			  ping.c \
+			  ping.h \
 			  presence.c \
 			  presence.h \
 			  roster.c \
@@ -34,7 +36,19 @@
 			  si.c \
 			  si.h \
 			  xdata.c \
-			  xdata.h
+			  xdata.h \
+			  caps.c \
+			  caps.h \
+			  adhoccommands.c \
+			  adhoccommands.h \
+			  pep.c \
+			  pep.h \
+			  usermood.c \
+			  usermood.h \
+			  usernick.c \
+			  usernick.h \
+			  usertune.c \
+			  usertune.h
 
 AM_CFLAGS = $(st)
 
--- a/libpurple/protocols/jabber/Makefile.mingw	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -42,8 +42,11 @@
 ##
 ##  SOURCES, OBJECTS
 ##
-C_SRC =			auth.c \
+C_SRC =	\
+			adhoccommands.c \
+			auth.c \
 			buddy.c \
+			caps.c \
 			chat.c \
 			disco.c \
 			google.c \
@@ -53,9 +56,14 @@
 			message.c \
 			oob.c \
 			parser.c \
+			pep.c \
+			ping.c \
 			presence.c \
 			roster.c \
 			si.c \
+			usermood.c \
+			usernick.c \
+			usertune.c \
 			xdata.c \
 			win32/posix.uname.c
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/adhoccommands.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,309 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "adhoccommands.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "xdata.h"
+#include "iq.h"
+#include "request.h"
+
+static void do_adhoc_ignoreme(JabberStream *js, ...) {
+	/* we don't have to do anything */
+}
+
+typedef struct _JabberAdHocActionInfo {
+	char *sessionid;
+	char *who;
+	char *node;
+	GList *actionslist;
+} JabberAdHocActionInfo;
+
+void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	const char *from = xmlnode_get_attrib(packet, "from");
+	const char *type = xmlnode_get_attrib(packet, "type");
+	const char *node;
+	xmlnode *query, *item;
+	JabberID *jabberid;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr = NULL;
+	
+	if(strcmp(type, "result"))
+		return;
+	
+	query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items");
+	if(!query)
+		return;
+	node = xmlnode_get_attrib(query,"node");
+	if(!node || strcmp(node, "http://jabber.org/protocol/commands"))
+		return;
+	
+	if((jabberid = jabber_id_new(from))) {
+		if(jabberid->resource && (jb = jabber_buddy_find(js, from, TRUE)))
+			jbr = jabber_buddy_find_resource(jb, jabberid->resource);
+		jabber_id_free(jabberid);
+	}
+	
+	if(!jbr)
+		return;
+	
+	if(jbr->commands) {
+		/* since the list we just received is complete, wipe the old one */
+		while(jbr->commands) {
+			JabberAdHocCommands *cmd = jbr->commands->data;
+			g_free(cmd->jid);
+			g_free(cmd->node);
+			g_free(cmd->name);
+			g_free(cmd);
+			jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
+		}
+	}
+	
+	for(item = query->child; item; item = item->next) {
+		JabberAdHocCommands *cmd;
+		if(item->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(strcmp(item->name, "item"))
+			continue;
+		cmd = g_new0(JabberAdHocCommands, 1);
+		
+		cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid"));
+		cmd->node = g_strdup(xmlnode_get_attrib(item,"node"));
+		cmd->name = g_strdup(xmlnode_get_attrib(item,"name"));
+		
+		jbr->commands = g_list_append(jbr->commands,cmd);
+	}
+}
+
+static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data);
+
+static void do_adhoc_action_cb(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data) {
+	xmlnode *command;
+	GList *action;
+	JabberAdHocActionInfo *actionInfo = user_data;
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+	jabber_iq_set_callback(iq, jabber_adhoc_parse, NULL);
+	
+	xmlnode_set_attrib(iq->node, "to", actionInfo->who);
+	command = xmlnode_new_child(iq->node,"command");
+	xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
+	xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid);
+	xmlnode_set_attrib(command,"node",actionInfo->node);
+	if(actionhandle)
+		xmlnode_set_attrib(command,"action",actionhandle);
+	xmlnode_insert_child(command,result);
+	
+	for(action = actionInfo->actionslist; action; action = g_list_next(action)) {
+		char *handle = action->data;
+		g_free(handle);
+	}
+	g_list_free(actionInfo->actionslist);
+	g_free(actionInfo->sessionid);
+	g_free(actionInfo->who);
+	g_free(actionInfo->node);
+	
+	jabber_iq_send(iq);
+}
+
+static void jabber_adhoc_parse(JabberStream *js, xmlnode *packet, gpointer data) {
+	xmlnode *command = xmlnode_get_child_with_namespace(packet, "command", "http://jabber.org/protocol/commands");
+	const char *status = xmlnode_get_attrib(command,"status");
+	xmlnode *xdata = xmlnode_get_child_with_namespace(command,"x","jabber:x:data");
+	const char *type = xmlnode_get_attrib(packet,"type");
+	
+	if(type && !strcmp(type,"error")) {
+		char *msg = jabber_parse_error(js, packet);
+		if(!msg)
+			msg = g_strdup(_("Unknown Error"));
+		
+		purple_notify_error(NULL, _("Ad-Hoc Command Failed"),
+							_("Ad-Hoc Command Failed"), msg);
+		g_free(msg);
+		return;
+	}
+	if(!type || strcmp(type,"result"))
+		return;
+	
+	if(!status)
+		return;
+	
+	if(!strcmp(status,"completed")) {
+		/* display result */
+		xmlnode *note = xmlnode_get_child(command,"note");
+		
+		if(note)
+			purple_notify_info(NULL, xmlnode_get_attrib(packet, "from"), xmlnode_get_data(note), NULL);
+		
+		if(xdata)
+			jabber_x_data_request(js, xdata, (jabber_x_data_cb)do_adhoc_ignoreme, NULL);
+		return;
+	}
+	if(!strcmp(status,"executing")) {
+		/* this command needs more steps */
+		xmlnode *actions, *action;
+		int actionindex = 0;
+		GList *actionslist = NULL;
+		JabberAdHocActionInfo *actionInfo;
+		if(!xdata)
+			return; /* shouldn't happen */
+		
+		actions = xmlnode_get_child(command,"actions");
+		if(!actions) {
+			JabberXDataAction *defaultaction = g_new0(JabberXDataAction, 1);
+			defaultaction->name = g_strdup(_("execute"));
+			defaultaction->handle = g_strdup("execute");
+			actionslist = g_list_append(actionslist, defaultaction);
+		} else {
+			const char *defaultactionhandle = xmlnode_get_attrib(actions, "execute");
+			int index = 0;
+			for(action = actions->child; action; action = action->next, ++index) {
+				if(action->type == XMLNODE_TYPE_TAG) {
+					JabberXDataAction *newaction = g_new0(JabberXDataAction, 1);
+					newaction->name = g_strdup(_(action->name));
+					newaction->handle = g_strdup(action->name);
+					actionslist = g_list_append(actionslist, newaction);
+					if(defaultactionhandle && !strcmp(defaultactionhandle, action->name))
+						actionindex = index;
+				}
+			}
+		}
+		
+		actionInfo = g_new0(JabberAdHocActionInfo, 1);
+		actionInfo->sessionid = g_strdup(xmlnode_get_attrib(command,"sessionid"));
+		actionInfo->who = g_strdup(xmlnode_get_attrib(packet,"from"));
+		actionInfo->node = g_strdup(xmlnode_get_attrib(command,"node"));
+		actionInfo->actionslist = actionslist;
+		
+		jabber_x_data_request_with_actions(js,xdata,actionslist,actionindex,do_adhoc_action_cb,actionInfo);
+	}
+}
+
+void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data) {
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		JabberAdHocCommands *cmd = data;
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		JabberStream *js = purple_account_get_connection(buddy->account)->proto_data;
+		
+		jabber_adhoc_execute(js, cmd);
+	}
+}
+
+static void jabber_adhoc_server_got_list_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", "http://jabber.org/protocol/disco#items");
+	xmlnode *item;
+	
+	if(!query)
+		return;
+
+	/* clean current list (just in case there is one) */
+	while(js->commands) {
+		JabberAdHocCommands *cmd = js->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->node);
+		g_free(cmd);
+		js->commands = g_list_delete_link(js->commands, js->commands);
+	}
+	
+	/* re-fill list */
+	for(item = query->child; item; item = item->next) {
+		JabberAdHocCommands *cmd;
+		if(item->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(strcmp(item->name, "item"))
+			continue;
+		cmd = g_new0(JabberAdHocCommands, 1);
+		cmd->jid = g_strdup(xmlnode_get_attrib(item,"jid"));
+		cmd->node = g_strdup(xmlnode_get_attrib(item,"node"));
+		cmd->name = g_strdup(xmlnode_get_attrib(item,"name"));
+		
+		js->commands = g_list_append(js->commands,cmd);
+	}
+}
+
+void jabber_adhoc_server_get_list(JabberStream *js) {
+	JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#items");
+	xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
+	
+	xmlnode_set_attrib(iq->node,"to",js->user->domain);
+	xmlnode_set_attrib(query,"node","http://jabber.org/protocol/commands");
+	
+	jabber_iq_set_callback(iq,jabber_adhoc_server_got_list_cb,NULL);
+	jabber_iq_send(iq);
+}
+
+void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd) {
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_SET);
+	xmlnode *command = xmlnode_new_child(iq->node,"command");
+	xmlnode_set_attrib(iq->node,"to",cmd->jid);
+	xmlnode_set_namespace(command,"http://jabber.org/protocol/commands");
+	xmlnode_set_attrib(command,"node",cmd->node);
+	xmlnode_set_attrib(command,"action","execute");
+	
+	jabber_iq_set_callback(iq,jabber_adhoc_parse,NULL);
+	
+	jabber_iq_send(iq);
+}
+
+static void jabber_adhoc_server_execute(PurplePluginAction *action) {
+	JabberAdHocCommands *cmd = action->user_data;
+	if(cmd) {
+		PurpleConnection *gc = (PurpleConnection *) action->context;
+		JabberStream *js = gc->proto_data;
+		
+		jabber_adhoc_execute(js, cmd);
+	}
+}
+
+void jabber_adhoc_init_server_commands(JabberStream *js, GList **m) {
+	GList *cmdlst;
+	JabberBuddy *jb;
+	
+	/* also add commands for other clients connected to the same account on another resource */
+	char *accountname = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+	if((jb = jabber_buddy_find(js, accountname, TRUE))) {
+		GList *iter;
+		for(iter = jb->resources; iter; iter = g_list_next(iter)) {
+			JabberBuddyResource *jbr = iter->data;
+			GList *riter;
+			for(riter = jbr->commands; riter; riter = g_list_next(riter)) {
+				JabberAdHocCommands *cmd = riter->data;
+				char *cmdname = g_strdup_printf("%s (%s)",cmd->name,jbr->name);
+				PurplePluginAction *act = purple_plugin_action_new(cmdname, jabber_adhoc_server_execute);
+				act->user_data = cmd;
+				*m = g_list_append(*m, act);
+				g_free(cmdname);
+			}
+		}
+	}
+	g_free(accountname);
+	
+	/* now add server commands */
+	for(cmdlst = js->commands; cmdlst; cmdlst = g_list_next(cmdlst)) {
+		JabberAdHocCommands *cmd = cmdlst->data;
+		PurplePluginAction *act = purple_plugin_action_new(cmd->name, jabber_adhoc_server_execute);
+		act->user_data = cmd;
+		*m = g_list_append(*m, act);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/adhoccommands.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,39 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_ADHOCCOMMANDS_H_
+#define _PURPLE_JABBER_ADHOCCOMMANDS_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0050 */
+
+void jabber_adhoc_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data);
+
+void jabber_adhoc_execute(JabberStream *js, JabberAdHocCommands *cmd);
+
+void jabber_adhoc_execute_action(PurpleBlistNode *node, gpointer data);
+
+void jabber_adhoc_server_get_list(JabberStream *js);
+
+void jabber_adhoc_init_server_commands(JabberStream *js, GList **m);
+
+#endif /* _PURPLE_JABBER_ADHOCCOMMANDS_H_ */
--- a/libpurple/protocols/jabber/auth.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -203,8 +203,15 @@
 	return TRUE;
 }
 	
-static void auth_pass_cb(JabberStream *js, PurpleRequestFields *fields)
+static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
 {
+	JabberStream *js;
+
+	/* The password prompt dialog doesn't get disposed if the account disconnects */
+	if (!PURPLE_CONNECTION_IS_VALID(conn))
+		return;
+
+	js = conn->proto_data;
 
 	if (!auth_pass_generic(js, fields))
 		return;
@@ -217,8 +224,16 @@
 }
 
 static void
-auth_old_pass_cb(JabberStream *js, PurpleRequestFields *fields)
+auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
 {
+	JabberStream *js;
+
+	/* The password prompt dialog doesn't get disposed if the account disconnects */
+	if (!PURPLE_CONNECTION_IS_VALID(conn))
+		return;
+
+	js = conn->proto_data;
+
 	if (!auth_pass_generic(js, fields))
 		return;
 	
@@ -228,9 +243,17 @@
 
 
 static void
-auth_no_pass_cb(JabberStream *js, PurpleRequestFields *fields)
+auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
 {
-	purple_connection_error(js->gc, _("Password is required to sign on."));
+	JabberStream *js;
+
+	/* The password prompt dialog doesn't get disposed if the account disconnects */
+	if (!PURPLE_CONNECTION_IS_VALID(conn))
+		return;
+
+	js = conn->proto_data;
+
+	purple_connection_error(conn, _("Password is required to sign on."));
 }
 
 static void jabber_auth_start_cyrus(JabberStream *js)
@@ -283,7 +306,7 @@
 				 */
 
 				if (!purple_account_get_password(js->gc->account)) {
-					purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js);
+					purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
 					return;
 
 				/* If we've got a password, but aren't sending
@@ -597,7 +620,7 @@
 	 */
 	
 	if (!purple_account_get_password(js->gc->account)) {
-		purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js);
+		purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
 		return;
 	}
 #endif
--- a/libpurple/protocols/jabber/auth.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/auth.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_AUTH_H_
 #define _PURPLE_JABBER_AUTH_H_
--- a/libpurple/protocols/jabber/buddy.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -34,6 +34,8 @@
 #include "iq.h"
 #include "presence.h"
 #include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
 
 typedef struct {
 	long idle_seconds;
@@ -116,7 +118,6 @@
 		int priority, JabberBuddyState state, const char *status)
 {
 	JabberBuddyResource *jbr = jabber_buddy_find_resource(jb, resource);
-
 	if(!jbr) {
 		jbr = g_new0(JabberBuddyResource, 1);
 		jbr->jb = jb;
@@ -128,7 +129,7 @@
 	jbr->state = state;
 	if(jbr->status)
 		g_free(jbr->status);
-        if (status)
+	if (status)
 		jbr->status = g_markup_escape_text(status, -1);
 	else
 		jbr->status = NULL;
@@ -141,6 +142,17 @@
 	g_return_if_fail(jbr != NULL);
 
 	jbr->jb->resources = g_list_remove(jbr->jb->resources, jbr);
+	
+	while(jbr->commands) {
+		JabberAdHocCommands *cmd = jbr->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->name);
+		g_free(cmd);
+		jbr->commands = g_list_delete_link(jbr->commands, jbr->commands);
+	}
+	
+	jabber_caps_free_clientinfo(jbr->caps);
 
 	g_free(jbr->name);
 	g_free(jbr->status);
@@ -411,7 +423,7 @@
 		if ((img = purple_buddy_icons_find_account_icon(gc->account))) {
 			gconstpointer avatar_data;
 			gsize avatar_len;
-			xmlnode *photo, *binval;
+			xmlnode *photo, *binval, *type;
 			gchar *enc;
 			int i;
 			unsigned char hashval[20];
@@ -424,6 +436,8 @@
 				xmlnode_free(photo);
 			}
 			photo = xmlnode_new_child(vc_node, "PHOTO");
+			type = xmlnode_new_child(photo, "TYPE");
+			xmlnode_insert_data(type, "image/png", -1);
 			binval = xmlnode_new_child(photo, "BINVAL");
 			enc = purple_base64_encode(avatar_data, avatar_len);
 
@@ -452,9 +466,135 @@
 
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
+	PurplePresence *gpresence;
+	PurpleStatus *status;
+	
+	if(((JabberStream*)gc->proto_data)->pep) {
+		/* XEP-0084: User Avatars */
+		if(img) {
+			/* A PNG header, including the IHDR, but nothing else */
+			const struct {
+				guchar signature[8]; /* must be hex 89 50 4E 47 0D 0A 1A 0A */
+				struct {
+					guint32 length; /* must be 0x0d */
+					guchar type[4]; /* must be 'I' 'H' 'D' 'R' */
+					guint32 width;
+					guint32 height;
+					guchar bitdepth;
+					guchar colortype;
+					guchar compression;
+					guchar filter;
+					guchar interlace;
+				} ihdr;
+			} *png = purple_imgstore_get_data(img); /* ATTN: this is in network byte order! */
+
+			/* check if the data is a valid png file (well, at least to some extend) */
+			if(png->signature[0] == 0x89 &&
+			   png->signature[1] == 0x50 &&
+			   png->signature[2] == 0x4e &&
+			   png->signature[3] == 0x47 &&
+			   png->signature[4] == 0x0d &&
+			   png->signature[5] == 0x0a &&
+			   png->signature[6] == 0x1a &&
+			   png->signature[7] == 0x0a &&
+			   ntohl(png->ihdr.length) == 0x0d &&
+			   png->ihdr.type[0] == 'I' &&
+			   png->ihdr.type[1] == 'H' &&
+			   png->ihdr.type[2] == 'D' &&
+			   png->ihdr.type[3] == 'R') {
+				/* parse PNG header to get the size of the image (yes, this is required) */
+				guint32 width = ntohl(png->ihdr.width);
+				guint32 height = ntohl(png->ihdr.height);
+				xmlnode *publish, *item, *data, *metadata, *info;
+				char *lengthstring, *widthstring, *heightstring;
+				
+				/* compute the sha1 hash */
+				PurpleCipherContext *ctx;
+				unsigned char digest[20];
+				char *hash;
+				char *base64avatar;
+				
+				ctx = purple_cipher_context_new_by_name("sha1", NULL);
+				purple_cipher_context_append(ctx, purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+				
+				/* convert digest to a string */
+				hash = g_strdup_printf("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x",digest[0],digest[1],digest[2],digest[3],digest[4],digest[5],digest[6],digest[7],digest[8],digest[9],digest[10],digest[11],digest[12],digest[13],digest[14],digest[15],digest[16],digest[17],digest[18],digest[19]);
+				
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEDATA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				data = xmlnode_new_child(item, "data");
+				xmlnode_set_namespace(data,AVATARNAMESPACEDATA);
+				
+				base64avatar = purple_base64_encode(purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+				xmlnode_insert_data(data,base64avatar,-1);
+				g_free(base64avatar);
+				
+				/* publish the avatar itself */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				/* next step: publish the metadata */
+				publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				xmlnode_set_attrib(item, "id", hash);
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				info = xmlnode_new_child(metadata, "info");
+				xmlnode_set_attrib(info, "id", hash);
+				xmlnode_set_attrib(info, "type", "image/png");
+				lengthstring = g_strdup_printf("%u", (unsigned)purple_imgstore_get_size(img));
+				xmlnode_set_attrib(info, "bytes", lengthstring);
+				g_free(lengthstring);
+				widthstring = g_strdup_printf("%u", width);
+				xmlnode_set_attrib(info, "width", widthstring);
+				g_free(widthstring);
+				heightstring = g_strdup_printf("%u", height);
+				xmlnode_set_attrib(info, "height", heightstring);
+				g_free(lengthstring);
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+				
+				g_free(hash);
+			} else { /* if(img) */
+				/* remove the metadata */
+				xmlnode *metadata, *item;
+				xmlnode *publish = xmlnode_new("publish");
+				xmlnode_set_attrib(publish,"node",AVATARNAMESPACEMETA);
+				
+				item = xmlnode_new_child(publish, "item");
+				
+				metadata = xmlnode_new_child(item, "metadata");
+				xmlnode_set_namespace(metadata,AVATARNAMESPACEMETA);
+				
+				xmlnode_new_child(metadata, "stop");
+				
+				/* publish the metadata */
+				jabber_pep_publish((JabberStream*)gc->proto_data, publish);
+			}
+		} else {
+			purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+						 "jabber_set_buddy_icon received non-png data");
+		}
+	}
+
+	/* even when the image is not png, we can still publish the vCard, since this
+	   one doesn't require a specific image type */
+
+	/* publish vCard for those poor older clients */
 	jabber_set_info(gc, purple_account_get_user_info(gc->account));
 
-	jabber_presence_send(gc->account, NULL);
+	gpresence = purple_account_get_presence(gc->account);
+	status = purple_presence_get_active_status(gpresence);
+	jabber_presence_send(gc->account, status);
 }
 
 /*
@@ -659,6 +799,130 @@
 				purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 			}
 		}
+#if 0 
+		/* #if 0 this for now; I think this would be far more useful if we limited this to a particular set of features
+ 		 * of particular interest (-vv jumps out as one). As it is now, I don't picture people getting all excited: "Oh sweet crap!
+ 		 * So-and-so supports 'jabber:x:data' AND 'Collaborative Data Objects'!"
+ 		 */
+
+		if(jbr && jbr->caps) {
+			GString *tmp = g_string_new("");
+			GList *iter;
+			for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+				const char *feature = iter->data;
+				
+				if(!strcmp(feature, "jabber:iq:last"))
+					feature = _("Last Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+					feature = _("Service Discovery Info");
+				else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+					feature = _("Service Discovery Items");
+				else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+					feature = _("Extended Stanza Addressing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+					feature = _("Multi-User Chat");
+				else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+					feature = _("Multi-User Chat Extended Presence Information");
+				else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+					feature = _("In-Band Bytestreams");
+				else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+					feature = _("Ad-Hoc Commands");
+				else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+					feature = _("PubSub Service");
+				else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+					feature = _("SOCKS5 Bytestreams");
+				else if(!strcmp(feature, "jabber:x:oob"))
+					feature = _("Out of Band Data");
+				else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+					feature = _("XHTML-IM");
+				else if(!strcmp(feature, "jabber:iq:register"))
+					feature = _("In-Band Registration");
+				else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+					feature = _("User Location");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+					feature = _("User Avatar");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+					feature = _("Chat State Notifications");
+				else if(!strcmp(feature, "jabber:iq:version"))
+					feature = _("Software Version");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+					feature = _("Stream Initiation");
+				else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+					feature = _("File Transfer");
+				else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+					feature = _("User Mood");
+				else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+					feature = _("User Activity");
+				else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+					feature = _("Entity Capabilities");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+					feature = _("Encrypted Session Negotiations");
+				else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+					feature = _("User Tune");
+				else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+					feature = _("Roster Item Exchange");
+				else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+					feature = _("Reachability Address");
+				else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+					feature = _("User Profile");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+					feature = _("Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+					feature = _("Jingle Audio");
+				else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+					feature = _("User Nickname");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+					feature = _("Jingle ICE UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+					feature = _("Jingle ICE TCP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+					feature = _("Jingle Raw UDP");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+					feature = _("Jingle Video");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+					feature = _("Jingle DTMF");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+					feature = _("Message Receipts");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+					feature = _("Public Key Publishing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+					feature = _("User Chatting");
+				else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+					feature = _("User Browsing");
+				else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+					feature = _("User Gaming");
+				else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+					feature = _("User Viewing");
+				else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+					feature = _("Ping");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+					feature = _("Stanza Encryption");
+				else if(!strcmp(feature, "urn:xmpp:time"))
+					feature = _("Entity Time");
+				else if(!strcmp(feature, "urn:xmpp:delay"))
+					feature = _("Delayed Delivery");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+					feature = _("Collaborative Data Objects");
+				else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+					feature = _("File Repository and Sharing");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+					feature = _("STUN Service Discovery for Jingle");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+					feature = _("Simplified Encrypted Session Negotiation");
+				else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+					feature = _("Hop Check");
+				else if(g_str_has_suffix(feature, "+notify"))
+					feature = NULL;
+				if(feature)
+					g_string_append_printf(tmp, "%s<br/>", feature);
+			}
+
+			if(strlen(tmp->str) > 0)
+				purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+			
+			g_string_free(tmp, TRUE);
+		}
+#endif
 	} else {
 		for(resources = jbi->jb->resources; resources; resources = resources->next) {
 			char *purdy = NULL;
@@ -700,6 +964,125 @@
 					purple_notify_user_info_add_pair(user_info, _("Operating System"), jbr->client.os);
 				}
 			}
+#if 0
+			if(jbr && jbr->caps) {
+				GString *tmp = g_string_new("");
+				GList *iter;
+				for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+					const char *feature = iter->data;
+					
+					if(!strcmp(feature, "jabber:iq:last"))
+						feature = _("Last Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#info"))
+						feature = _("Service Discovery Info");
+					else if(!strcmp(feature, "http://jabber.org/protocol/disco#items"))
+						feature = _("Service Discovery Items");
+					else if(!strcmp(feature, "http://jabber.org/protocol/address"))
+						feature = _("Extended Stanza Addressing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc"))
+						feature = _("Multi-User Chat");
+					else if(!strcmp(feature, "http://jabber.org/protocol/muc#user"))
+						feature = _("Multi-User Chat Extended Presence Information");
+					else if(!strcmp(feature, "http://jabber.org/protocol/ibb"))
+						feature = _("In-Band Bytestreams");
+					else if(!strcmp(feature, "http://jabber.org/protocol/commands"))
+						feature = _("Ad-Hoc Commands");
+					else if(!strcmp(feature, "http://jabber.org/protocol/pubsub"))
+						feature = _("PubSub Service");
+					else if(!strcmp(feature, "http://jabber.org/protocol/bytestreams"))
+						feature = _("SOCKS5 Bytestreams");
+					else if(!strcmp(feature, "jabber:x:oob"))
+						feature = _("Out of Band Data");
+					else if(!strcmp(feature, "http://jabber.org/protocol/xhtml-im"))
+						feature = _("XHTML-IM");
+					else if(!strcmp(feature, "jabber:iq:register"))
+						feature = _("In-Band Registration");
+					else if(!strcmp(feature, "http://jabber.org/protocol/geoloc"))
+						feature = _("User Location");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0084.html"))
+						feature = _("User Avatar");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatstates"))
+						feature = _("Chat State Notifications");
+					else if(!strcmp(feature, "jabber:iq:version"))
+						feature = _("Software Version");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si"))
+						feature = _("Stream Initiation");
+					else if(!strcmp(feature, "http://jabber.org/protocol/si/profile/file-transfer"))
+						feature = _("File Transfer");
+					else if(!strcmp(feature, "http://jabber.org/protocol/mood"))
+						feature = _("User Mood");
+					else if(!strcmp(feature, "http://jabber.org/protocol/activity"))
+						feature = _("User Activity");
+					else if(!strcmp(feature, "http://jabber.org/protocol/caps"))
+						feature = _("Entity Capabilities");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html"))
+						feature = _("Encrypted Session Negotiations");
+					else if(!strcmp(feature, "http://jabber.org/protocol/tune"))
+						feature = _("User Tune");
+					else if(!strcmp(feature, "http://jabber.org/protocol/rosterx"))
+						feature = _("Roster Item Exchange");
+					else if(!strcmp(feature, "http://jabber.org/protocol/reach"))
+						feature = _("Reachability Address");
+					else if(!strcmp(feature, "http://jabber.org/protocol/profile"))
+						feature = _("User Profile");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0166.html#ns"))
+						feature = _("Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0167.html#ns"))
+						feature = _("Jingle Audio");
+					else if(!strcmp(feature, "http://jabber.org/protocol/nick"))
+						feature = _("User Nickname");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-udp"))
+						feature = _("Jingle ICE UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0176.html#ns-tcp"))
+						feature = _("Jingle ICE TCP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0177.html#ns"))
+						feature = _("Jingle Raw UDP");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0180.html#ns"))
+						feature = _("Jingle Video");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0181.html#ns"))
+						feature = _("Jingle DTMF");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0184.html#ns"))
+						feature = _("Message Receipts");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0189.html#ns"))
+						feature = _("Public Key Publishing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/chatting"))
+						feature = _("User Chatting");
+					else if(!strcmp(feature, "http://jabber.org/protocol/browsing"))
+						feature = _("User Browsing");
+					else if(!strcmp(feature, "http://jabber.org/protocol/gaming"))
+						feature = _("User Gaming");
+					else if(!strcmp(feature, "http://jabber.org/protocol/viewing"))
+						feature = _("User Viewing");
+					else if(!strcmp(feature, "urn:xmpp:ping") || !strcmp(feature, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+						feature = _("Ping");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0200.html#ns"))
+						feature = _("Stanza Encryption");
+					else if(!strcmp(feature, "urn:xmpp:time"))
+						feature = _("Entity Time");
+					else if(!strcmp(feature, "urn:xmpp:delay"))
+						feature = _("Delayed Delivery");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0204.html#ns"))
+						feature = _("Collaborative Data Objects");
+					else if(!strcmp(feature, "http://jabber.org/protocol/fileshare"))
+						feature = _("File Repository and Sharing");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0215.html#ns"))
+						feature = _("STUN Service Discovery for Jingle");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0116.html#ns"))
+						feature = _("Simplified Encrypted Session Negotiation");
+					else if(!strcmp(feature, "http://www.xmpp.org/extensions/xep-0219.html#ns"))
+						feature = _("Hop Check");
+					else if(g_str_has_suffix(feature, "+notify"))
+						feature = NULL;
+					
+					if(feature)
+						g_string_append_printf(tmp, "%s\n", feature);
+				}
+				if(strlen(tmp->str) > 0)
+					purple_notify_user_info_add_pair(user_info, _("Capabilities"), tmp->str);
+				
+				g_string_free(tmp, TRUE);
+			}
+#endif
 		}
 	}
 
@@ -777,6 +1160,17 @@
 	jabber_iq_send(iq);
 }
 
+static void
+jabber_string_escape_and_append(GString *string, const char *name, const char *value, gboolean indent)
+{
+	gchar *escaped;
+
+	escaped = g_markup_escape_text(value, -1);
+	g_string_append_printf(string, "%s<b>%s:</b> %s<br/>",
+			indent ? "&nbsp;&nbsp;" : "", name, escaped);
+	g_free(escaped);
+}
+
 static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data)
 {
 	const char *id, *from;
@@ -821,8 +1215,8 @@
 
 			text = xmlnode_get_data(child);
 			if(text && !strcmp(child->name, "FN")) {
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Full Name"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Full Name"), text, FALSE);
 			} else if(!strcmp(child->name, "N")) {
 				for(child2 = child->child; child2; child2 = child2->next)
 				{
@@ -833,17 +1227,14 @@
 
 					text2 = xmlnode_get_data(child2);
 					if(text2 && !strcmp(child2->name, "FAMILY")) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>",
-								_("Family Name"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Family Name"), text2, FALSE);
 					} else if(text2 && !strcmp(child2->name, "GIVEN")) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>",
-								_("Given Name"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Given Name"), text2, FALSE);
 					} else if(text2 && !strcmp(child2->name, "MIDDLE")) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>",
-								_("Middle Name"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Middle Name"), text2, FALSE);
 					}
 					g_free(text2);
 				}
@@ -852,11 +1243,11 @@
 				if(b) {
 					purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", text);
 				}
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Nickname"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Nickname"), text, FALSE);
 			} else if(text && !strcmp(child->name, "BDAY")) {
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Birthday"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Birthday"), text, FALSE);
 			} else if(!strcmp(child->name, "ADR")) {
 				gboolean address_line_added = FALSE;
 
@@ -881,34 +1272,27 @@
 					}
 
 					if(!strcmp(child2->name, "POBOX")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("P.O. Box"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("P.O. Box"), text2, TRUE);
 					} else if(!strcmp(child2->name, "EXTADR")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Extended Address"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Extended Address"), text2, TRUE);
 					} else if(!strcmp(child2->name, "STREET")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Street Address"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Street Address"), text2, TRUE);
 					} else if(!strcmp(child2->name, "LOCALITY")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Locality"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Locality"), text2, TRUE);
 					} else if(!strcmp(child2->name, "REGION")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Region"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Region"), text2, TRUE);
 					} else if(!strcmp(child2->name, "PCODE")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Postal Code"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Postal Code"), text2, TRUE);
 					} else if(!strcmp(child2->name, "CTRY")
 								|| !strcmp(child2->name, "COUNTRY")) {
-						g_string_append_printf(info_text,
-								"&nbsp;<b>%s:</b> %s<br/>",
-								_("Country"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Country"), text2, TRUE);
 					}
 					g_free(text2);
 				}
@@ -918,34 +1302,38 @@
 					/* show what kind of number it is */
 					number = xmlnode_get_data(child2);
 					if(number) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>", _("Telephone"), number);
+						jabber_string_escape_and_append(info_text,
+								_("Telephone"), number, FALSE);
 						g_free(number);
 					}
 				} else if((number = xmlnode_get_data(child))) {
 					/* lots of clients (including purple) do this, but it's
 					 * out of spec */
-					g_string_append_printf(info_text,
-							"<b>%s:</b> %s<br/>", _("Telephone"), number);
+					jabber_string_escape_and_append(info_text,
+							_("Telephone"), number, FALSE);
 					g_free(number);
 				}
 			} else if(!strcmp(child->name, "EMAIL")) {
-				char *userid;
+				char *userid, *escaped;
 				if((child2 = xmlnode_get_child(child, "USERID"))) {
 					/* show what kind of email it is */
 					userid = xmlnode_get_data(child2);
 					if(userid) {
+						escaped = g_markup_escape_text(userid, -1);
 						g_string_append_printf(info_text,
-								"<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
-								_("E-Mail"), userid, userid);
+								"<b>%s:</b> <a href=\"mailto:%s\">%s</a><br/>",
+								_("E-Mail"), escaped, escaped);
+						g_free(escaped);
 						g_free(userid);
 					}
 				} else if((userid = xmlnode_get_data(child))) {
 					/* lots of clients (including purple) do this, but it's
 					 * out of spec */
-						g_string_append_printf(info_text,
-								"<b>%s:</b> <a href='mailto:%s'>%s</a><br/>",
-								_("E-Mail"), userid, userid);
+					escaped = g_markup_escape_text(userid, -1);
+					g_string_append_printf(info_text,
+							"<b>%s:</b> <a href=\"mailto:%s\">%s</a><br/>",
+							_("E-Mail"), escaped, escaped);
+					g_free(escaped);
 					g_free(userid);
 				}
 			} else if(!strcmp(child->name, "ORG")) {
@@ -958,25 +1346,23 @@
 
 					text2 = xmlnode_get_data(child2);
 					if(text2 && !strcmp(child2->name, "ORGNAME")) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>",
-								_("Organization Name"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Organization Name"), text2, FALSE);
 					} else if(text2 && !strcmp(child2->name, "ORGUNIT")) {
-						g_string_append_printf(info_text,
-								"<b>%s:</b> %s<br/>",
-								_("Organization Unit"), text2);
+						jabber_string_escape_and_append(info_text,
+								_("Organization Unit"), text2, FALSE);
 					}
 					g_free(text2);
 				}
 			} else if(text && !strcmp(child->name, "TITLE")) {
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Title"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Title"), text, FALSE);
 			} else if(text && !strcmp(child->name, "ROLE")) {
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Role"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Role"), text, FALSE);
 			} else if(text && !strcmp(child->name, "DESC")) {
-				g_string_append_printf(info_text, "<b>%s:</b> %s<br/>",
-						_("Description"), text);
+				jabber_string_escape_and_append(info_text,
+						_("Description"), text, FALSE);
 			} else if(!strcmp(child->name, "PHOTO") ||
 					!strcmp(child->name, "LOGO")) {
 				char *bintext = NULL;
@@ -1023,6 +1409,109 @@
 	jabber_buddy_info_show_if_ready(jbi);
 }
 
+typedef struct _JabberBuddyAvatarUpdateURLInfo {
+	JabberStream *js;
+	char *from;
+	char *id;
+} JabberBuddyAvatarUpdateURLInfo;
+
+static void do_buddy_avatar_update_fromurl(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
+	JabberBuddyAvatarUpdateURLInfo *info = user_data;
+	if(!url_text) {
+		purple_debug(PURPLE_DEBUG_ERROR, "jabber",
+					 "do_buddy_avatar_update_fromurl got error \"%s\"", error_message);
+		return;
+	}
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(info->js->gc), info->from, (void*)url_text, len, info->id);
+	g_free(info->from);
+	g_free(info->id);
+	g_free(info);
+}
+
+static void do_buddy_avatar_update_data(JabberStream *js, const char *from, xmlnode *items) {
+	xmlnode *item, *data;
+	const char *checksum;
+	char *b64data;
+	void *img;
+	size_t size;
+	if(!items)
+		return;
+	
+	item = xmlnode_get_child(items, "item");
+	if(!item)
+		return;
+	
+	data = xmlnode_get_child_with_namespace(item,"data",AVATARNAMESPACEDATA);
+	if(!data)
+		return;
+	
+	checksum = xmlnode_get_attrib(item,"id");
+	if(!checksum)
+		return;
+	
+	b64data = xmlnode_get_data(data);
+	if(!b64data)
+		return;
+	
+	img = purple_base64_decode(b64data, &size);
+	if(!img)
+		return;
+	
+	purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, img, size, checksum);
+}
+
+void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items) {
+	PurpleBuddy *buddy = purple_find_buddy(purple_connection_get_account(js->gc), from);
+	const char *checksum;
+	xmlnode *item, *metadata;
+	if(!buddy)
+		return;
+	
+	checksum = purple_buddy_icons_get_checksum_for_user(buddy);
+	item = xmlnode_get_child(items,"item");
+	metadata = xmlnode_get_child_with_namespace(item, "metadata", AVATARNAMESPACEMETA);
+	if(!metadata)
+		return;
+	/* check if we have received a stop */
+	if(xmlnode_get_child(metadata, "stop")) {
+		purple_buddy_icons_set_for_user(purple_connection_get_account(js->gc), from, NULL, 0, NULL);
+	} else {
+		xmlnode *info, *goodinfo = NULL;
+		
+		/* iterate over all info nodes to get one we can use */
+		for(info = metadata->child; info; info = info->next) {
+			if(info->type == XMLNODE_TYPE_TAG && !strcmp(info->name,"info")) {
+				const char *type = xmlnode_get_attrib(info,"type");
+				const char *id = xmlnode_get_attrib(info,"id");
+				
+				if(checksum && id && !strcmp(id, checksum)) {
+					/* we already have that avatar, so we don't have to do anything */
+					goodinfo = NULL;
+					break;
+				}
+				/* We'll only pick the png one for now. It's a very nice image format anyways. */
+				if(type && id && !goodinfo && !strcmp(type, "image/png"))
+					goodinfo = info;
+			}
+		}
+		if(goodinfo) {
+			const char *url = xmlnode_get_attrib(goodinfo,"url");
+			const char *id = xmlnode_get_attrib(goodinfo,"id");
+			
+			/* the avatar might either be stored in a pep node, or on a HTTP/HTTPS URL */
+			if(!url)
+				jabber_pep_request_item(js, from, AVATARNAMESPACEDATA, id, do_buddy_avatar_update_data);
+			else {
+				JabberBuddyAvatarUpdateURLInfo *info = g_new0(JabberBuddyAvatarUpdateURLInfo, 1);
+				info->js = js;
+				info->from = g_strdup(from);
+				info->id = g_strdup(id);
+				purple_util_fetch_url(url, TRUE, NULL, TRUE, do_buddy_avatar_update_fromurl, info);
+			}
+		}
+	}
+}
 
 static void jabber_buddy_info_resource_free(gpointer data)
 {
@@ -1295,7 +1784,7 @@
 	status    = purple_presence_get_active_status(gpresence);
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(js, state, msg, priority);
 
 	g_free(msg);
 
@@ -1389,12 +1878,54 @@
 	jabber_presence_subscription_set(js, buddy->name, "unsubscribe");
 }
 
+static void jabber_buddy_login(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed presence of the current status */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		PurpleConnection *gc = purple_account_get_connection(buddy->account);
+		JabberStream *js = gc->proto_data;
+		PurpleAccount *account = purple_connection_get_account(gc);
+		PurplePresence *gpresence = purple_account_get_presence(account);
+		PurpleStatus *status = purple_presence_get_active_status(gpresence);
+		xmlnode *presence;
+		JabberBuddyState state;
+		char *msg;
+		int priority;
+		
+		purple_status_to_jabber(status, &state, &msg, &priority);
+		presence = jabber_presence_create_js(js, state, msg, priority);
+		
+		g_free(msg);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
+
+static void jabber_buddy_logout(PurpleBlistNode *node, gpointer data) {
+	if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		/* simply create a directed unavailable presence */
+		PurpleBuddy *buddy = (PurpleBuddy *) node;
+		JabberStream *js = purple_account_get_connection(buddy->account)->proto_data;
+		xmlnode *presence;
+		
+		presence = jabber_presence_create_js(js, JABBER_BUDDY_STATE_UNAVAILABLE, NULL, 0);
+		
+		xmlnode_set_attrib(presence, "to", buddy->name);
+		
+		jabber_send(js, presence);
+		xmlnode_free(presence);
+	}
+}
 
 static GList *jabber_buddy_menu(PurpleBuddy *buddy)
 {
 	PurpleConnection *gc = purple_account_get_connection(buddy->account);
 	JabberStream *js = gc->proto_data;
 	JabberBuddy *jb = jabber_buddy_find(js, buddy->name, TRUE);
+	GList *jbrs;
 
 	GList *m = NULL;
 	PurpleMenuAction *act;
@@ -1439,6 +1970,38 @@
 		                           NULL, NULL);
 		m = g_list_append(m, act);
 	}
+	
+	/*
+	 * This if-condition implements parts of XEP-0100: Gateway Interaction
+	 *
+	 * According to stpeter, there is no way to know if a jid on the roster is a gateway without sending a disco#info.
+	 * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume
+	 * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since
+	 * people don't tend to have a server or other service there.
+	 */
+	if (g_utf8_strchr(buddy->name, -1, '@') == NULL) {
+		act = purple_menu_action_new(_("Log In"),
+									 PURPLE_CALLBACK(jabber_buddy_login),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+		act = purple_menu_action_new(_("Log Out"),
+									 PURPLE_CALLBACK(jabber_buddy_logout),
+									 NULL, NULL);
+		m = g_list_append(m, act);
+	}
+	
+	/* add all ad hoc commands to the action menu */
+	for(jbrs = jb->resources; jbrs; jbrs = g_list_next(jbrs)) {
+		JabberBuddyResource *jbr = jbrs->data;
+		GList *commands;
+		if (!jbr->commands)
+			continue;
+		for(commands = jbr->commands; commands; commands = g_list_next(commands)) {
+			JabberAdHocCommands *cmd = commands->data;
+			act = purple_menu_action_new(cmd->name, PURPLE_CALLBACK(jabber_adhoc_execute_action), cmd, NULL);
+			m = g_list_append(m, act);
+		}
+	}
 
 	return m;
 }
@@ -1584,36 +2147,58 @@
 	results = purple_notify_searchresults_new();
 	if((x = xmlnode_get_child_with_namespace(query, "x", "jabber:x:data"))) {
 		xmlnode *reported;
+		GSList *column_vars = NULL;
+
 		purple_debug_info("jabber", "new-skool\n");
+
 		if((reported = xmlnode_get_child(x, "reported"))) {
 			xmlnode *field = xmlnode_get_child(reported, "field");
 			while(field) {
-				/* XXX keep track of this order, use it below */
 				const char *var = xmlnode_get_attrib(field, "var");
 				const char *label = xmlnode_get_attrib(field, "label");
 				if(var) {
 					column = purple_notify_searchresults_column_new(label ? label : var);
 					purple_notify_searchresults_column_add(results, column);
+					column_vars = g_slist_append(column_vars, (char *)var);
 				}
 				field = xmlnode_get_next_twin(field);
 			}
 		}
+
 		item = xmlnode_get_child(x, "item");
 		while(item) {
 			GList *row = NULL;
-			field = xmlnode_get_child(item, "field");
-			while(field) {
-				xmlnode *valuenode = xmlnode_get_child(field, "value");
-				if(valuenode) {
-					char *value = xmlnode_get_data(valuenode);
-					row = g_list_append(row, value);
+			GSList *l;
+			xmlnode *valuenode;
+			const char *var;
+
+			for (l = column_vars; l != NULL; l = l->next) {
+				/*
+				 * Build a row containing the strings that correspond
+				 * to each column of the search results.
+				 */
+				for (field = xmlnode_get_child(item, "field");
+						field != NULL;
+						field = xmlnode_get_next_twin(field))
+				{
+					if ((var = xmlnode_get_attrib(field, "var")) &&
+							!strcmp(var, l->data) &&
+							(valuenode = xmlnode_get_child(field, "value")))
+					{
+						char *value = xmlnode_get_data(valuenode);
+						row = g_list_append(row, value);
+						break;
+					}
 				}
-				field = xmlnode_get_next_twin(field);
+				if (field == NULL)
+					/* No data for this column */
+					row = g_list_append(row, NULL);
 			}
 			purple_notify_searchresults_row_add(results, row);
-
 			item = xmlnode_get_next_twin(item);
 		}
+
+		g_slist_free(column_vars);
 	} else {
 		/* old skool */
 		purple_debug_info("jabber", "old-skool\n");
@@ -1728,10 +2313,10 @@
  * in purple-i18n@lists.sourceforge.net (March 2006)
  */
 static const char * jabber_user_dir_comments [] = {
-       /* current comment from Jabber User Directory users.jabber.org */
-       N_("Find a contact by entering the search criteria in the given fields. "
-          "Note: Each field supports wild card searches (%)"),
-       NULL
+	/* current comment from Jabber User Directory users.jabber.org */
+	N_("Find a contact by entering the search criteria in the given fields. "
+	   "Note: Each field supports wild card searches (%)"),
+	NULL
 };
 #endif
 
@@ -1824,14 +2409,14 @@
 				_("Search for XMPP users"), instructions, fields,
 				_("Search"), G_CALLBACK(user_search_cb),
 				_("Cancel"), G_CALLBACK(user_search_cancel_cb),
-				NULL, NULL, NULL,
+				purple_connection_get_account(js->gc), NULL, NULL,
 				"account", usi);
 
 		g_free(instructions);
 	}
 }
 
-static void jabber_user_search_ok(JabberStream *js, const char *directory)
+void jabber_user_search(JabberStream *js, const char *directory)
 {
 	JabberIq *iq;
 
@@ -1858,7 +2443,7 @@
 			_("Select a user directory to search"),
 			js->user_directories ? js->user_directories->data : NULL,
 			FALSE, FALSE, NULL,
-			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search_ok),
+			_("Search Directory"), PURPLE_CALLBACK(jabber_user_search),
 			_("Cancel"), NULL,
 			NULL, NULL, NULL,
 			"account", js);
--- a/libpurple/protocols/jabber/buddy.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,13 +17,11 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_BUDDY_H_
 #define _PURPLE_JABBER_BUDDY_H_
 
-#include "jabber.h"
-
 typedef enum {
 	JABBER_BUDDY_STATE_UNKNOWN = -2,
 	JABBER_BUDDY_STATE_ERROR = -1,
@@ -35,6 +33,12 @@
 	JABBER_BUDDY_STATE_DND
 } JabberBuddyState;
 
+#include "jabber.h"
+#include "caps.h"
+
+#define AVATARNAMESPACEDATA "http://www.xmpp.org/extensions/xep-0084.html#ns-data"
+#define AVATARNAMESPACEMETA "http://www.xmpp.org/extensions/xep-0084.html#ns-metadata"
+
 typedef struct _JabberBuddy {
 	GList *resources;
 	char *error_msg;
@@ -53,6 +57,12 @@
 	} subscription;
 } JabberBuddy;
 
+typedef struct _JabberAdHocCommands {
+	char *jid;
+	char *node;
+	char *name;
+} JabberAdHocCommands;
+
 typedef struct _JabberBuddyResource {
 	JabberBuddy *jb;
 	char *name;
@@ -71,6 +81,8 @@
 		char *name;
 		char *os;
 	} client;
+	JabberCapsClientInfo *caps;
+	GList *commands;
 } JabberBuddyResource;
 
 void jabber_buddy_free(JabberBuddy *jb);
@@ -92,6 +104,7 @@
 void jabber_set_info(PurpleConnection *gc, const char *info);
 void jabber_setup_set_info(PurplePluginAction *action);
 void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img);
+void jabber_buddy_avatar_update_metadata(JabberStream *js, const char *from, xmlnode *items);
 
 const char *jabber_buddy_state_get_name(JabberBuddyState state);
 const char *jabber_buddy_state_get_status_id(JabberBuddyState state);
@@ -99,6 +112,7 @@
 JabberBuddyState jabber_buddy_status_id_get_state(const char *id);
 JabberBuddyState jabber_buddy_show_get_state(const char *id);
 
+void jabber_user_search(JabberStream *js, const char *directory);
 void jabber_user_search_begin(PurplePluginAction *);
 
 void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/caps.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,557 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "caps.h"
+#include <string.h>
+#include "internal.h"
+#include "util.h"
+#include "iq.h"
+
+#define JABBER_CAPS_FILENAME "xmpp-caps.xml"
+
+static GHashTable *capstable = NULL; /* JabberCapsKey -> JabberCapsValue */
+
+typedef struct _JabberCapsKey {
+	char *node;
+	char *ver;
+} JabberCapsKey;
+
+typedef struct _JabberCapsValueExt {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+} JabberCapsValueExt;
+
+typedef struct _JabberCapsValue {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+	GHashTable *ext; /* char * -> JabberCapsValueExt */
+} JabberCapsValue;
+
+static guint jabber_caps_hash(gconstpointer key) {
+	const JabberCapsKey *name = key;
+	guint nodehash = g_str_hash(name->node);
+	guint verhash = g_str_hash(name->ver);
+	
+	return nodehash ^ verhash;
+}
+
+static gboolean jabber_caps_compare(gconstpointer v1, gconstpointer v2) {
+	const JabberCapsKey *name1 = v1;
+	const JabberCapsKey *name2 = v2;
+
+	return strcmp(name1->node,name2->node) == 0 && strcmp(name1->ver,name2->ver) == 0;
+}
+
+static void jabber_caps_destroy_key(gpointer key) {
+	JabberCapsKey *keystruct = key;
+	g_free(keystruct->node);
+	g_free(keystruct->ver);
+	g_free(keystruct);
+}
+
+static void jabber_caps_destroy_value(gpointer value) {
+	JabberCapsValue *valuestruct = value;
+	while(valuestruct->identities) {
+		JabberCapsIdentity *id = valuestruct->identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->name);
+		g_free(id);
+		
+		valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities);
+	}
+	while(valuestruct->features) {
+		g_free(valuestruct->features->data);
+		valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features);
+	}
+	g_hash_table_destroy(valuestruct->ext);
+	g_free(valuestruct);
+}
+
+static void jabber_caps_ext_destroy_value(gpointer value) {
+	JabberCapsValueExt *valuestruct = value;
+	while(valuestruct->identities) {
+		JabberCapsIdentity *id = valuestruct->identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->name);
+		g_free(id);
+		
+		valuestruct->identities = g_list_delete_link(valuestruct->identities,valuestruct->identities);
+	}
+	while(valuestruct->features) {
+		g_free(valuestruct->features->data);
+		valuestruct->features = g_list_delete_link(valuestruct->features,valuestruct->features);
+	}
+	g_free(valuestruct);
+}
+
+static void jabber_caps_load(void);
+
+void jabber_caps_init(void) {
+	capstable = g_hash_table_new_full(jabber_caps_hash, jabber_caps_compare, jabber_caps_destroy_key, jabber_caps_destroy_value);
+	jabber_caps_load();
+}
+
+static void jabber_caps_load(void) {
+	xmlnode *capsdata = purple_util_read_xml_from_file(JABBER_CAPS_FILENAME, "XMPP capabilities cache");
+	xmlnode *client;
+
+	if(!capsdata)
+		return;
+
+	if (strcmp(capsdata->name, "capabilities") != 0) {
+		xmlnode_free(capsdata);
+		return;
+	}
+	
+	for(client = capsdata->child; client; client = client->next) {
+		if(client->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(!strcmp(client->name, "client")) {
+			JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+			JabberCapsValue *value = g_new0(JabberCapsValue, 1);
+			xmlnode *child;
+			key->node = g_strdup(xmlnode_get_attrib(client,"node"));
+			key->ver  = g_strdup(xmlnode_get_attrib(client,"ver"));
+			value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value);
+			for(child = client->child; child; child = child->next) {
+				if(child->type != XMLNODE_TYPE_TAG)
+					continue;
+				if(!strcmp(child->name,"feature")) {
+					const char *var = xmlnode_get_attrib(child, "var");
+					if(!var)
+						continue;
+					value->features = g_list_append(value->features,g_strdup(var));
+				} else if(!strcmp(child->name,"identity")) {
+					const char *category = xmlnode_get_attrib(child, "category");
+					const char *type = xmlnode_get_attrib(child, "type");
+					const char *name = xmlnode_get_attrib(child, "name");
+					
+					JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+					id->category = g_strdup(category);
+					id->type = g_strdup(type);
+					id->name = g_strdup(name);
+					
+					value->identities = g_list_append(value->identities,id);
+				} else if(!strcmp(child->name,"ext")) {
+					const char *identifier = xmlnode_get_attrib(child, "identifier");
+					if(identifier) {
+						xmlnode *extchild;
+						
+						JabberCapsValueExt *extvalue = g_new0(JabberCapsValueExt, 1);
+						
+						for(extchild = child->child; extchild; extchild = extchild->next) {
+							if(extchild->type != XMLNODE_TYPE_TAG)
+								continue;
+							if(!strcmp(extchild->name,"feature")) {
+								const char *var = xmlnode_get_attrib(extchild, "var");
+								if(!var)
+									continue;
+								extvalue->features = g_list_append(extvalue->features,g_strdup(var));
+							} else if(!strcmp(extchild->name,"identity")) {
+								const char *category = xmlnode_get_attrib(extchild, "category");
+								const char *type = xmlnode_get_attrib(extchild, "type");
+								const char *name = xmlnode_get_attrib(extchild, "name");
+								
+								JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+								id->category = g_strdup(category);
+								id->type = g_strdup(type);
+								id->name = g_strdup(name);
+								
+								extvalue->identities = g_list_append(extvalue->identities,id);
+							}
+						}
+						g_hash_table_replace(value->ext, g_strdup(identifier), extvalue);
+					}
+				}
+			}
+			g_hash_table_replace(capstable, key, value);
+		}
+	}
+	xmlnode_free(capsdata);
+}
+
+static void jabber_caps_store_ext(gpointer key, gpointer value, gpointer user_data) {
+	const char *extname = key;
+	JabberCapsValueExt *props = value;
+	xmlnode *root = user_data;
+	xmlnode *ext = xmlnode_new_child(root,"ext");
+	GList *iter;
+
+	xmlnode_set_attrib(ext,"identifier",extname);
+
+	for(iter = props->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		xmlnode *identity = xmlnode_new_child(ext, "identity");
+		xmlnode_set_attrib(identity, "category", id->category);
+		xmlnode_set_attrib(identity, "type", id->type);
+		if (id->name)
+			xmlnode_set_attrib(identity, "name", id->name);
+	}
+
+	for(iter = props->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		xmlnode *feature = xmlnode_new_child(ext, "feature");
+		xmlnode_set_attrib(feature, "var", feat);
+	}
+}
+
+static void jabber_caps_store_client(gpointer key, gpointer value, gpointer user_data) {
+	JabberCapsKey *clientinfo = key;
+	JabberCapsValue *props = value;
+	xmlnode *root = user_data;
+	xmlnode *client = xmlnode_new_child(root,"client");
+	GList *iter;
+
+	xmlnode_set_attrib(client,"node",clientinfo->node);
+	xmlnode_set_attrib(client,"ver",clientinfo->ver);
+	
+	for(iter = props->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		xmlnode *identity = xmlnode_new_child(client, "identity");
+		xmlnode_set_attrib(identity, "category", id->category);
+		xmlnode_set_attrib(identity, "type", id->type);
+		xmlnode_set_attrib(identity, "name", id->name);
+	}
+
+	for(iter = props->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		xmlnode *feature = xmlnode_new_child(client, "feature");
+		xmlnode_set_attrib(feature, "var", feat);
+	}
+	
+	g_hash_table_foreach(props->ext,jabber_caps_store_ext,client);
+}
+
+static void jabber_caps_store(void) {
+	char *str;
+	xmlnode *root = xmlnode_new("capabilities");
+	g_hash_table_foreach(capstable, jabber_caps_store_client, root);
+	str = xmlnode_to_formatted_str(root, NULL);
+	xmlnode_free(root);
+	purple_util_write_data_to_file(JABBER_CAPS_FILENAME, str, -1);
+	g_free(str);
+}
+
+/* this function assumes that all information is available locally */
+static JabberCapsClientInfo *jabber_caps_collect_info(const char *node, const char *ver, GList *ext) {
+	JabberCapsClientInfo *result = g_new0(JabberCapsClientInfo, 1);
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	JabberCapsValue *caps;
+	GList *iter;
+	
+	key->node = (char *)node;
+	key->ver = (char *)ver;
+	
+	caps = g_hash_table_lookup(capstable,key);
+	
+	g_free(key);
+	
+	/* join all information */
+	for(iter = caps->identities; iter; iter = g_list_next(iter)) {
+		JabberCapsIdentity *id = iter->data;
+		JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1);
+		newid->category = g_strdup(id->category);
+		newid->type = g_strdup(id->type);
+		newid->name = g_strdup(id->name);
+		
+		result->identities = g_list_append(result->identities,newid);
+	}
+	for(iter = caps->features; iter; iter = g_list_next(iter)) {
+		const char *feat = iter->data;
+		char *newfeat = g_strdup(feat);
+		
+		result->features = g_list_append(result->features,newfeat);
+	}
+	
+	for(iter = ext; iter; iter = g_list_next(iter)) {
+		const char *extname = iter->data;
+		JabberCapsValueExt *extinfo = g_hash_table_lookup(caps->ext,extname);
+		
+		if(extinfo) {
+			GList *iter2;
+			for(iter2 = extinfo->identities; iter2; iter2 = g_list_next(iter2)) {
+				JabberCapsIdentity *id = iter2->data;
+				JabberCapsIdentity *newid = g_new0(JabberCapsIdentity, 1);
+				newid->category = g_strdup(id->category);
+				newid->type = g_strdup(id->type);
+				newid->name = g_strdup(id->name);
+				
+				result->identities = g_list_append(result->identities,newid);
+			}
+			for(iter2 = extinfo->features; iter2; iter2 = g_list_next(iter2)) {
+				const char *feat = iter2->data;
+				char *newfeat = g_strdup(feat);
+				
+				result->features = g_list_append(result->features,newfeat);
+			}
+		}
+	}
+	return result;
+}
+
+void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo) {
+	if(!clientinfo)
+		return;
+	while(clientinfo->identities) {
+		JabberCapsIdentity *id = clientinfo->identities->data;
+		g_free(id->category);
+		g_free(id->type);
+		g_free(id->name);
+		g_free(id);
+		
+		clientinfo->identities = g_list_delete_link(clientinfo->identities,clientinfo->identities);
+	}
+	while(clientinfo->features) {
+		char *feat = clientinfo->features->data;
+		g_free(feat);
+		
+		clientinfo->features = g_list_delete_link(clientinfo->features,clientinfo->features);
+	}
+	
+	g_free(clientinfo);
+}
+
+typedef struct _jabber_caps_cbplususerdata {
+	jabber_caps_get_info_cb cb;
+	gpointer user_data;
+	
+	char *who;
+	char *node;
+	char *ver;
+	GList *ext;
+	unsigned extOutstanding;
+} jabber_caps_cbplususerdata;
+
+typedef struct jabber_ext_userdata {
+	jabber_caps_cbplususerdata *userdata;
+	char *node;
+} jabber_ext_userdata;
+
+static void jabber_caps_get_info_check_completion(jabber_caps_cbplususerdata *userdata) {
+	if(userdata->extOutstanding == 0) {
+		userdata->cb(jabber_caps_collect_info(userdata->node, userdata->ver, userdata->ext), userdata->user_data);
+		g_free(userdata->who);
+		g_free(userdata->node);
+		g_free(userdata->ver);
+		while(userdata->ext) {
+			g_free(userdata->ext->data);
+			userdata->ext = g_list_delete_link(userdata->ext,userdata->ext);
+		}
+		g_free(userdata);
+	}
+}
+
+static void jabber_caps_ext_iqcb(JabberStream *js, xmlnode *packet, gpointer data) {
+	/* collect data and fetch all exts */
+	xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info");
+	xmlnode *child;
+	jabber_ext_userdata *extuserdata = data;
+	jabber_caps_cbplususerdata *userdata = extuserdata->userdata;
+	JabberCapsValue *client;
+	const char *node = extuserdata->node;
+	const char *key;
+	
+	--userdata->extOutstanding;
+	
+	if(node) {
+		JabberCapsValueExt *value = g_new0(JabberCapsValueExt, 1);
+
+		JabberCapsKey *clientkey = g_new0(JabberCapsKey, 1);
+		clientkey->node = userdata->node;
+		clientkey->ver = userdata->ver;
+
+		client = g_hash_table_lookup(capstable,clientkey);
+
+		g_free(clientkey);
+
+		/* split node by #, key either points to \0 or the correct ext afterwards */
+		for(key = node; key[0] != '\0'; ++key) {
+			if(key[0] == '#') {
+				++key;
+				break;
+			}
+		}
+
+		for(child = query->child; child; child = child->next) {
+			if(child->type != XMLNODE_TYPE_TAG)
+				continue;
+			if(!strcmp(child->name,"feature")) {
+				const char *var = xmlnode_get_attrib(child, "var");
+				if(!var)
+					continue;
+				value->features = g_list_append(value->features,g_strdup(var));
+			} else if(!strcmp(child->name,"identity")) {
+				const char *category = xmlnode_get_attrib(child, "category");
+				const char *type = xmlnode_get_attrib(child, "type");
+				const char *name = xmlnode_get_attrib(child, "name");
+				
+				JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+				id->category = g_strdup(category);
+				id->type = g_strdup(type);
+				id->name = g_strdup(name);
+				
+				value->identities = g_list_append(value->identities,id);
+			}
+		}
+		g_hash_table_replace(client->ext, g_strdup(key), value);
+		
+		jabber_caps_store();
+	}
+	g_free(extuserdata->node);
+	g_free(extuserdata);
+	jabber_caps_get_info_check_completion(userdata);
+}
+
+static void jabber_caps_client_iqcb(JabberStream *js, xmlnode *packet, gpointer data) {
+	/* collect data and fetch all exts */
+	xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#info");
+	xmlnode *child;
+	GList *iter;
+	jabber_caps_cbplususerdata *userdata = data;
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	JabberCapsValue *value = g_new0(JabberCapsValue, 1);
+	key->node = g_strdup(userdata->node);
+	key->ver = g_strdup(userdata->ver);
+	
+	value->ext = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, jabber_caps_ext_destroy_value);
+
+	for(child = query->child; child; child = child->next) {
+		if(child->type != XMLNODE_TYPE_TAG)
+			continue;
+		if(!strcmp(child->name,"feature")) {
+			const char *var = xmlnode_get_attrib(child, "var");
+			if(!var)
+				continue;
+			value->features = g_list_append(value->features,g_strdup(var));
+		} else if(!strcmp(child->name,"identity")) {
+			const char *category = xmlnode_get_attrib(child, "category");
+			const char *type = xmlnode_get_attrib(child, "type");
+			const char *name = xmlnode_get_attrib(child, "name");
+			
+			JabberCapsIdentity *id = g_new0(JabberCapsIdentity, 1);
+			id->category = g_strdup(category);
+			id->type = g_strdup(type);
+			id->name = g_strdup(name);
+			
+			value->identities = g_list_append(value->identities,id);
+		}
+	}
+	g_hash_table_replace(capstable, key, value);
+				
+	/* fetch all exts */
+	for(iter = userdata->ext; iter; iter = g_list_next(iter)) {
+		JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info");
+		xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info");
+		char *node = g_strdup_printf("%s#%s", userdata->node, (const char*)iter->data);
+		jabber_ext_userdata *ext_data = g_new0(jabber_ext_userdata, 1);	
+		ext_data->node = node;
+		ext_data->userdata = userdata;
+
+		xmlnode_set_attrib(query, "node", node);
+		xmlnode_set_attrib(iq->node, "to", userdata->who);
+
+		jabber_iq_set_callback(iq,jabber_caps_ext_iqcb,ext_data);
+		jabber_iq_send(iq);
+	}
+	
+	jabber_caps_store();
+	
+	jabber_caps_get_info_check_completion(userdata);
+}
+
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data) {
+	JabberCapsValue *client;
+	JabberCapsKey *key = g_new0(JabberCapsKey, 1);
+	char *originalext = g_strdup(ext);
+	jabber_caps_cbplususerdata *userdata = g_new0(jabber_caps_cbplususerdata, 1);
+	userdata->cb = cb;
+	userdata->user_data = user_data;
+	userdata->who = g_strdup(who);
+	userdata->node = g_strdup(node);
+	userdata->ver = g_strdup(ver);
+
+	if(originalext) {
+		int i;
+		gchar **splat = g_strsplit(originalext, " ", 0);
+		for(i =0; splat[i]; i++) {
+			userdata->ext = g_list_append(userdata->ext, splat[i]);
+			++userdata->extOutstanding;
+		}
+		g_free(splat);
+	}
+	g_free(originalext);
+
+	key->node = (char *)node;
+	key->ver = (char *)ver;
+
+	client = g_hash_table_lookup(capstable, key);
+
+	g_free(key);
+
+	if(!client) {
+		JabberIq *iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info");
+		xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info");
+		char *nodever = g_strdup_printf("%s#%s", node, ver);
+		xmlnode_set_attrib(query, "node", nodever);
+		g_free(nodever);
+		xmlnode_set_attrib(iq->node, "to", who);
+
+		jabber_iq_set_callback(iq,jabber_caps_client_iqcb,userdata);
+		jabber_iq_send(iq);
+	} else {
+		GList *iter;
+		/* fetch unknown exts only */
+		for(iter = userdata->ext; iter; iter = g_list_next(iter)) {
+			JabberCapsValueExt *extvalue = g_hash_table_lookup(client->ext, (const char*)iter->data);
+			JabberIq *iq;
+			xmlnode *query;
+			char *nodever;
+			jabber_ext_userdata *ext_data;
+
+			if(extvalue) {
+				/* we already have this ext, don't bother with it */
+				--userdata->extOutstanding;
+				continue;
+			}
+
+			ext_data = g_new0(jabber_ext_userdata, 1);
+
+			iq = jabber_iq_new_query(js,JABBER_IQ_GET,"http://jabber.org/protocol/disco#info");
+			query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#info");
+			nodever = g_strdup_printf("%s#%s", node, (const char*)iter->data);
+			xmlnode_set_attrib(query, "node", nodever);
+			xmlnode_set_attrib(iq->node, "to", who);
+
+			ext_data->node = nodever;
+			ext_data->userdata = userdata;
+
+			jabber_iq_set_callback(iq, jabber_caps_ext_iqcb, ext_data);
+			jabber_iq_send(iq);
+		}
+		/* maybe we have all data available anyways? This is the ideal case where no network traffic is necessary */
+		jabber_caps_get_info_check_completion(userdata);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/caps.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,49 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_CAPS_H_
+#define _PURPLE_JABBER_CAPS_H_
+
+typedef struct _JabberCapsClientInfo JabberCapsClientInfo;
+
+#include "jabber.h"
+
+/* Implementation of XEP-0115 */
+
+typedef struct _JabberCapsIdentity {
+	char *category;
+	char *type;
+	char *name;
+} JabberCapsIdentity;
+
+struct _JabberCapsClientInfo {
+	GList *identities; /* JabberCapsIdentity */
+	GList *features; /* char * */
+};
+
+typedef void (*jabber_caps_get_info_cb)(JabberCapsClientInfo *info, gpointer user_data);
+
+void jabber_caps_init(void);
+
+void jabber_caps_get_info(JabberStream *js, const char *who, const char *node, const char *ver, const char *ext, jabber_caps_get_info_cb cb, gpointer user_data);
+void jabber_caps_free_clientinfo(JabberCapsClientInfo *clientinfo);
+
+#endif /* _PURPLE_JABBER_CAPS_H_ */
--- a/libpurple/protocols/jabber/chat.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/chat.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -261,7 +261,7 @@
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
 
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(js, state, msg, priority);
 	full_jid = g_strdup_printf("%s/%s", room_jid, handle);
 	xmlnode_set_attrib(presence, "to", full_jid);
 	g_free(full_jid);
@@ -634,7 +634,7 @@
 
 	purple_status_to_jabber(status, &state, &msg, &priority);
 
-	presence = jabber_presence_create(state, msg, priority);
+	presence = jabber_presence_create_js(chat->js, state, msg, priority);
 	full_jid = g_strdup_printf("%s@%s/%s", chat->room, chat->server, nick);
 	xmlnode_set_attrib(presence, "to", full_jid);
 	g_free(full_jid);
@@ -833,12 +833,18 @@
 
 gboolean jabber_chat_ban_user(JabberChat *chat, const char *who, const char *why)
 {
+	JabberChatMember *jcm;
+	const char *jid;
+	char *to;
 	JabberIq *iq;
-	JabberChatMember *jcm = g_hash_table_lookup(chat->members, who);
-	char *to;
 	xmlnode *query, *item, *reason;
 
-	if(!jcm || !jcm->jid)
+	jcm = g_hash_table_lookup(chat->members, who);
+	if (jcm && jcm->jid)
+		jid = jcm->jid;
+	else if (g_utf8_strchr(who, -1, '@') != NULL)
+		jid = who;
+	else
 		return FALSE;
 
 	iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
@@ -850,7 +856,7 @@
 
 	query = xmlnode_get_child(iq->node, "query");
 	item = xmlnode_new_child(query, "item");
-	xmlnode_set_attrib(item, "jid", jcm->jid);
+	xmlnode_set_attrib(item, "jid", jid);
 	xmlnode_set_attrib(item, "affiliation", "outcast");
 	if(why) {
 		reason = xmlnode_new_child(item, "reason");
@@ -864,14 +870,18 @@
 
 gboolean jabber_chat_affiliate_user(JabberChat *chat, const char *who, const char *affiliation)
 {
+	JabberChatMember *jcm;
+	const char *jid;
 	char *to;
 	JabberIq *iq;
 	xmlnode *query, *item;
-	JabberChatMember *jcm;
 
 	jcm = g_hash_table_lookup(chat->members, who);
-
-	if (!jcm || !jcm->jid)
+	if (jcm && jcm->jid)
+		jid = jcm->jid;
+	else if (g_utf8_strchr(who, -1, '@') != NULL)
+		jid = who;
+	else
 		return FALSE;
 
 	iq = jabber_iq_new_query(chat->js, JABBER_IQ_SET,
@@ -883,7 +893,7 @@
 
 	query = xmlnode_get_child(iq->node, "query");
 	item = xmlnode_new_child(query, "item");
-	xmlnode_set_attrib(item, "jid", jcm->jid);
+	xmlnode_set_attrib(item, "jid", jid);
 	xmlnode_set_attrib(item, "affiliation", affiliation);
 
 	jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/chat.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/chat.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_CHAT_H_
 #define _PURPLE_JABBER_CHAT_H_
--- a/libpurple/protocols/jabber/disco.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/disco.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -30,15 +30,19 @@
 #include "jabber.h"
 #include "presence.h"
 #include "roster.h"
+#include "pep.h"
+#include "adhoccommands.h"
+
 
 struct _jabber_disco_info_cb_data {
 	gpointer data;
 	JabberDiscoInfoCallback *callback;
 };
 
-#define SUPPORT_FEATURE(x) \
+#define SUPPORT_FEATURE(x) { \
 	feature = xmlnode_new_child(query, "feature"); \
-	xmlnode_set_attrib(feature, "var", x);
+	xmlnode_set_attrib(feature, "var", x); \
+}
 
 
 void jabber_disco_info_parse(JabberStream *js, xmlnode *packet) {
@@ -72,7 +76,6 @@
 			xmlnode_set_attrib(query, "node", node);
 
 		if(!node || !strcmp(node, CAPS0115_NODE "#" VERSION)) {
-
 			identity = xmlnode_new_child(query, "identity");
 			xmlnode_set_attrib(identity, "category", "client");
 			xmlnode_set_attrib(identity, "type", "pc"); /* XXX: bot, console,
@@ -98,18 +101,62 @@
 			SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer")
 			SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im")
 			SUPPORT_FEATURE("urn:xmpp:ping")
+			SUPPORT_FEATURE("http://www.xmpp.org/extensions/xep-0199.html#ns")
+			
+			if(!node) { /* non-caps disco#info, add all enabled extensions */
+				GList *features;
+				for(features = jabber_features; features; features = features->next) {
+					JabberFeature *feat = (JabberFeature*)features->data;
+					if(feat->is_enabled == NULL || feat->is_enabled(js, feat->shortname, feat->namespace) == TRUE)
+						SUPPORT_FEATURE(feat->namespace);
+				}
+			}
 		} else {
-			xmlnode *error, *inf;
-
-			/* XXX: gross hack, implement jabber_iq_set_type or something */
-			xmlnode_set_attrib(iq->node, "type", "error");
-			iq->type = JABBER_IQ_ERROR;
-
-			error = xmlnode_new_child(query, "error");
-			xmlnode_set_attrib(error, "code", "404");
-			xmlnode_set_attrib(error, "type", "cancel");
-			inf = xmlnode_new_child(error, "item-not-found");
-			xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+			const char *ext = NULL;
+			unsigned pos;
+			unsigned nodelen = strlen(node);
+			unsigned capslen = strlen(CAPS0115_NODE);
+			/* do a basic plausability check */
+			if(nodelen > capslen+1) {
+				/* verify that the string is CAPS0115#<ext> and get the pointer to the ext part */
+				for(pos = 0; pos < capslen+1; ++pos) {
+					if(pos == capslen) {
+						if(node[pos] == '#')
+							ext = &node[pos+1];
+						else
+							break;
+					} else if(node[pos] != CAPS0115_NODE[pos])
+					break;
+				}
+				
+				if(ext != NULL) {
+					/* look for that ext */
+					GList *features;
+					for(features = jabber_features; features; features = features->next) {
+						JabberFeature *feat = (JabberFeature*)features->data;
+						if(!strcmp(feat->shortname, ext)) {
+							SUPPORT_FEATURE(feat->namespace);
+							break;
+						}
+					}
+					if(features == NULL)
+						ext = NULL;
+				}
+			}
+			
+			if(ext == NULL) {
+				xmlnode *error, *inf;
+				
+				/* XXX: gross hack, implement jabber_iq_set_type or something */
+				xmlnode_set_attrib(iq->node, "type", "error");
+				iq->type = JABBER_IQ_ERROR;
+				
+				error = xmlnode_new_child(query, "error");
+				xmlnode_set_attrib(error, "code", "404");
+				xmlnode_set_attrib(error, "type", "cancel");
+				inf = xmlnode_new_child(error, "item-not-found");
+				xmlnode_set_namespace(inf, "urn:ietf:params:xml:ns:xmpp-stanzas");
+			}
 		}
 
 		jabber_iq_send(iq);
@@ -165,6 +212,11 @@
 					capabilities |= JABBER_CAP_IQ_SEARCH;
 				else if(!strcmp(var, "jabber:iq:register"))
 					capabilities |= JABBER_CAP_IQ_REGISTER;
+				else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
+					capabilities |= JABBER_CAP_PING;
+				else if(!strcmp(var, "http://jabber.org/protocol/commands")) {
+					capabilities |= JABBER_CAP_ADHOC;
+				}
 			}
 		}
 
@@ -208,6 +260,17 @@
 	if(type && !strcmp(type, "get")) {
 		JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_RESULT,
 				"http://jabber.org/protocol/disco#items");
+		
+		/* preserve node */
+		xmlnode *iq_query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
+		if(iq_query) {
+			xmlnode *query = xmlnode_get_child_with_namespace(packet,"query","http://jabber.org/protocol/disco#items");
+			if(query) {
+				const char *node = xmlnode_get_attrib(query,"node");
+				if(node)
+					xmlnode_set_attrib(iq_query,"node",node);
+			}
+		}
 
 		jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
 
@@ -227,7 +290,13 @@
 		jabber_roster_request(js);
 	}
 
-	/* when we get the roster back, we'll send our initial presence */
+	/* Send initial presence; this will trigger receipt of presence for contacts on the roster */
+	jabber_presence_send(js->gc->account, NULL);
+	
+	if (js->server_caps & JABBER_CAP_ADHOC) {
+		/* The server supports ad-hoc commands, so let's request the list */
+		jabber_adhoc_server_get_list(js);
+	}
 }
 
 static void
@@ -260,9 +329,11 @@
 	     child = xmlnode_get_next_twin(child)) {
 		const char *category, *type, *name;
 		category = xmlnode_get_attrib(child, "category");
+		type = xmlnode_get_attrib(child, "type");
+		if(category && type && !strcmp(category, "pubsub") && !strcmp(type,"pep"))
+			js->pep = TRUE;
 		if (!category || strcmp(category, "server"))
 			continue;
-		type = xmlnode_get_attrib(child, "type");
 		if (!type || strcmp(type, "im"))
 			continue;
 
@@ -273,7 +344,7 @@
 		g_free(js->server_name);
 		js->server_name = g_strdup(name);
 		if (!strcmp(name, "Google Talk")) {
-		  purple_debug_info("jabber", "Google Talk!");
+		  purple_debug_info("jabber", "Google Talk!\n");
 		  js->googletalk = TRUE;
 		}
 	}
@@ -291,6 +362,8 @@
 		} else if (!strcmp("google:roster", var)) {
 			js->server_caps |= JABBER_CAP_GOOGLE_ROSTER;
 			jabber_google_roster_init(js);
+		} else if (!strcmp("http://jabber.org/protocol/commands", var)) {
+			js->server_caps |= JABBER_CAP_ADHOC;
 		}
 	}
 
--- a/libpurple/protocols/jabber/disco.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/disco.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_DISCO_H_
 #define _PURPLE_JABBER_DISCO_H_
--- a/libpurple/protocols/jabber/google.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/google.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "internal.h"
--- a/libpurple/protocols/jabber/google.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/google.h	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #ifndef _PURPLE_GOOGLE_H_
--- a/libpurple/protocols/jabber/iq.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/iq.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -31,6 +31,8 @@
 #include "oob.h"
 #include "roster.h"
 #include "si.h"
+#include "ping.h"
+#include "adhoccommands.h"
 
 #ifdef _WIN32
 #include "utsname.h"
@@ -343,6 +345,13 @@
 		jabber_gmail_poke(js, packet);
 		return;
 	}
+	
+	purple_debug_info("jabber", "jabber_iq_parse\n");
+
+	if(xmlnode_get_child_with_namespace(packet, "ping", "urn:xmpp:ping")) {
+		jabber_ping_parse(js, packet);
+		return;
+	}
 
 	/* If we get here, send the default error reply mandated by XMPP-CORE */
 	if(type && (!strcmp(type, "set") || !strcmp(type, "get"))) {
--- a/libpurple/protocols/jabber/iq.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/iq.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_IQ_H_
 #define _PURPLE_JABBER_IQ_H_
--- a/libpurple/protocols/jabber/jabber.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -51,12 +51,20 @@
 #include "presence.h"
 #include "jabber.h"
 #include "roster.h"
+#include "ping.h"
 #include "si.h"
 #include "xdata.h"
+#include "pep.h"
+#include "adhoccommands.h"
 
-#define JABBER_CONNECT_STEPS (js->gsc ? 8 : 5)
+#include <assert.h>
+
+#define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
 
 static PurplePlugin *my_protocol = NULL;
+GList *jabber_features;
+
+static void jabber_unregister_account_cb(JabberStream *js);
 
 static void jabber_stream_init(JabberStream *js)
 {
@@ -80,6 +88,8 @@
 	const char *type = xmlnode_get_attrib(packet, "type");
 	if(type && !strcmp(type, "result")) {
 		jabber_stream_set_state(js, JABBER_STREAM_CONNECTED);
+		if(js->unregistration)
+			jabber_unregister_account_cb(js);
 	} else {
 		purple_connection_error(js->gc, _("Error initializing session"));
 	}
@@ -132,6 +142,9 @@
 	if(xmlnode_get_child(packet, "starttls")) {
 		if(jabber_process_starttls(js, packet))
 			return;
+	} else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) {
+		purple_connection_error(js->gc, _("You require encryption, but it is not available on this server."));
+		return;
 	}
 
 	if(js->registration) {
@@ -169,49 +182,49 @@
 
 static void tls_init(JabberStream *js);
 
-void jabber_process_packet(JabberStream *js, xmlnode *packet)
+void jabber_process_packet(JabberStream *js, xmlnode **packet)
 {
 	const char *xmlns;
 
-	purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, &packet);
+	purple_signal_emit(my_protocol, "jabber-receiving-xmlnode", js->gc, packet);
 
 	/* if the signal leaves us with a null packet, we're done */
-	if(NULL == packet)
+	if(NULL == *packet)
 		return;
 
-	xmlns = xmlnode_get_namespace(packet);
+	xmlns = xmlnode_get_namespace(*packet);
 
-	if(!strcmp(packet->name, "iq")) {
-		jabber_iq_parse(js, packet);
-	} else if(!strcmp(packet->name, "presence")) {
-		jabber_presence_parse(js, packet);
-	} else if(!strcmp(packet->name, "message")) {
-		jabber_message_parse(js, packet);
-	} else if(!strcmp(packet->name, "stream:features")) {
-		jabber_stream_features_parse(js, packet);
-	} else if (!strcmp(packet->name, "features") &&
+	if(!strcmp((*packet)->name, "iq")) {
+		jabber_iq_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "presence")) {
+		jabber_presence_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "message")) {
+		jabber_message_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "stream:features")) {
+		jabber_stream_features_parse(js, *packet);
+	} else if (!strcmp((*packet)->name, "features") &&
 		   !strcmp(xmlns, "http://etherx.jabber.org/streams")) {
-		jabber_stream_features_parse(js, packet);
-	} else if(!strcmp(packet->name, "stream:error") ||
-			 (!strcmp(packet->name, "error") &&
+		jabber_stream_features_parse(js, *packet);
+	} else if(!strcmp((*packet)->name, "stream:error") ||
+			 (!strcmp((*packet)->name, "error") &&
 				!strcmp(xmlns, "http://etherx.jabber.org/streams")))
 	{
-		jabber_stream_handle_error(js, packet);
-	} else if(!strcmp(packet->name, "challenge")) {
+		jabber_stream_handle_error(js, *packet);
+	} else if(!strcmp((*packet)->name, "challenge")) {
 		if(js->state == JABBER_STREAM_AUTHENTICATING)
-			jabber_auth_handle_challenge(js, packet);
-	} else if(!strcmp(packet->name, "success")) {
+			jabber_auth_handle_challenge(js, *packet);
+	} else if(!strcmp((*packet)->name, "success")) {
 		if(js->state == JABBER_STREAM_AUTHENTICATING)
-			jabber_auth_handle_success(js, packet);
-	} else if(!strcmp(packet->name, "failure")) {
+			jabber_auth_handle_success(js, *packet);
+	} else if(!strcmp((*packet)->name, "failure")) {
 		if(js->state == JABBER_STREAM_AUTHENTICATING)
-			jabber_auth_handle_failure(js, packet);
-	} else if(!strcmp(packet->name, "proceed")) {
+			jabber_auth_handle_failure(js, *packet);
+	} else if(!strcmp((*packet)->name, "proceed")) {
 		if(js->state == JABBER_STREAM_AUTHENTICATING && !js->gsc)
 			tls_init(js);
 	} else {
 		purple_debug(PURPLE_DEBUG_WARNING, "jabber", "Unknown packet: %s\n",
-				packet->name);
+				(*packet)->name);
 	}
 }
 
@@ -453,6 +466,9 @@
 		jabber_send_raw(js, "<?xml version='1.0' ?>", -1);
 	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING);
 	purple_ssl_input_add(gsc, jabber_recv_cb_ssl, gc);
+	
+	/* Tell the app that we're doing encryption */
+	jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
 }
 
 
@@ -494,29 +510,20 @@
 	js = gc->proto_data;
 	js->gsc = NULL;
 
-	switch(error) {
-		case PURPLE_SSL_CONNECT_FAILED:
-			purple_connection_error(gc, _("Connection Failed"));
-			break;
-		case PURPLE_SSL_HANDSHAKE_FAILED:
-			purple_connection_error(gc, _("SSL Handshake Failed"));
-			break;
-	}
+	purple_connection_error(gc, purple_ssl_strerror(error));
 }
 
 static void tls_init(JabberStream *js)
 {
 	purple_input_remove(js->gc->inpa);
 	js->gc->inpa = 0;
-	js->gsc = purple_ssl_connect_fd(js->gc->account, js->fd,
-			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc);
+	js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd,
+			jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc);
 }
 
 static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port)
 {
-#ifdef HAVE_CYRUS_SASL
 	js->serverFQDN = g_strdup(fqdn);
-#endif
 
 	if (purple_proxy_connect(js->gc, js->gc->account, host,
 			port, jabber_login_callback, js->gc) == NULL)
@@ -563,6 +570,7 @@
 	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
 	js->write_buffer = purple_circ_buffer_new(512);
+	js->old_length = -1;
 
 	if(!js->user) {
 		purple_connection_error(gc, _("Invalid XMPP ID"));
@@ -623,6 +631,8 @@
 	JabberStream *js = data;
 	PurpleAccount *account = purple_connection_get_account(js->gc);
 
+	jabber_parser_free(js);
+
 	purple_account_disconnect(account);
 
 	return FALSE;
@@ -637,12 +647,21 @@
 static void
 jabber_registration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
 	const char *type = xmlnode_get_attrib(packet, "type");
 	char *buf;
+	char *to = data;
 
 	if(!strcmp(type, "result")) {
+		if(js->registration) {
 		buf = g_strdup_printf(_("Registration of %s@%s successful"),
 				js->user->node, js->user->domain);
+			if(account->registration_cb)
+				(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
+		}
+		else
+			buf = g_strdup_printf(_("Registration to %s successful"),
+				to);
 		purple_notify_info(NULL, _("Registration Successful"),
 				_("Registration Successful"), buf);
 		g_free(buf);
@@ -655,20 +674,56 @@
 		purple_notify_error(NULL, _("Registration Failed"),
 				_("Registration Failed"), msg);
 		g_free(msg);
+		if(account->registration_cb)
+			(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
 	}
+	g_free(to);
+	if(js->registration)
 	jabber_connection_schedule_close(js);
 }
 
 static void
-jabber_register_cb(JabberStream *js, PurpleRequestFields *fields)
+jabber_unregistration_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	const char *type = xmlnode_get_attrib(packet, "type");
+	char *buf;
+	char *to = data;
+	
+	if(!strcmp(type, "result")) {
+		buf = g_strdup_printf(_("Registration from %s successfully removed"),
+							  to);
+		purple_notify_info(NULL, _("Unregistration Successful"),
+						   _("Unregistration Successful"), buf);
+		g_free(buf);
+	} else {
+		char *msg = jabber_parse_error(js, packet);
+		
+		if(!msg)
+			msg = g_strdup(_("Unknown Error"));
+		
+		purple_notify_error(NULL, _("Unregistration Failed"),
+							_("Unregistration Failed"), msg);
+		g_free(msg);
+	}
+	g_free(to);
+}
+
+typedef struct _JabberRegisterCBData {
+	JabberStream *js;
+	char *who;
+} JabberRegisterCBData;
+
+static void
+jabber_register_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
 {
 	GList *groups, *flds;
 	xmlnode *query, *y;
 	JabberIq *iq;
 	char *username;
 
-	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
+	iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
 	query = xmlnode_get_child(iq->node, "query");
+	xmlnode_set_attrib(iq->node,"to",cbdata->who);
 
 	for(groups = purple_request_fields_get_groups(fields); groups;
 			groups = groups->next) {
@@ -676,6 +731,24 @@
 				flds; flds = flds->next) {
 			PurpleRequestField *field = flds->data;
 			const char *id = purple_request_field_get_id(field);
+			if(!strcmp(id,"unregister")) {
+				gboolean value = purple_request_field_bool_get_value(field);
+				if(value) {
+					/* unregister from service. this doesn't include any of the fields, so remove them from the stanza by recreating it
+					   (there's no "remove child" function for xmlnode) */
+					jabber_iq_free(iq);
+					iq = jabber_iq_new_query(cbdata->js, JABBER_IQ_SET, "jabber:iq:register");
+					query = xmlnode_get_child(iq->node, "query");
+					xmlnode_set_attrib(iq->node,"to",cbdata->who);
+					xmlnode_new_child(query, "remove");
+					
+					jabber_iq_set_callback(iq, jabber_unregistration_result_cb, cbdata->who);
+					
+					jabber_iq_send(iq);
+					g_free(cbdata);
+					return;
+				}
+			} else {
 			const char *value = purple_request_field_string_get_value(field);
 
 			if(!strcmp(id, "username")) {
@@ -710,73 +783,97 @@
 				continue;
 			}
 			xmlnode_insert_data(y, value, -1);
-			if(!strcmp(id, "username")) {
-				if(js->user->node)
-					g_free(js->user->node);
-				js->user->node = g_strdup(value);
+				if(cbdata->js->registration && !strcmp(id, "username")) {
+					if(cbdata->js->user->node)
+						g_free(cbdata->js->user->node);
+					cbdata->js->user->node = g_strdup(value);
 			}
+				if(cbdata->js->registration && !strcmp(id, "password"))
+					purple_account_set_password(cbdata->js->gc->account, value);
 		}
 	}
+	}
 
-	username = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain,
-			js->user->resource);
-	purple_account_set_username(js->gc->account, username);
+	if(cbdata->js->registration) {
+		username = g_strdup_printf("%s@%s/%s", cbdata->js->user->node, cbdata->js->user->domain,
+				cbdata->js->user->resource);
+		purple_account_set_username(cbdata->js->gc->account, username);
 	g_free(username);
+	}
 
-	jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL);
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, cbdata->who);
 
 	jabber_iq_send(iq);
-
+	g_free(cbdata);
 }
 
 static void
-jabber_register_cancel_cb(JabberStream *js, PurpleRequestFields *fields)
+jabber_register_cancel_cb(JabberRegisterCBData *cbdata, PurpleRequestFields *fields)
 {
-	jabber_connection_schedule_close(js);
+	PurpleAccount *account = purple_connection_get_account(cbdata->js->gc);
+	if(account && cbdata->js->registration) {
+		if(account->registration_cb)
+			(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
+		jabber_connection_schedule_close(cbdata->js);
+}
+	g_free(cbdata->who);
+	g_free(cbdata);
 }
 
 static void jabber_register_x_data_cb(JabberStream *js, xmlnode *result, gpointer data)
 {
 	xmlnode *query;
 	JabberIq *iq;
+	char *to = data;
 
 	iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:register");
 	query = xmlnode_get_child(iq->node, "query");
+	xmlnode_set_attrib(iq->node,"to",to);
 
 	xmlnode_insert_child(query, result);
 
-	jabber_iq_set_callback(iq, jabber_registration_result_cb, NULL);
+	jabber_iq_set_callback(iq, jabber_registration_result_cb, to);
 	jabber_iq_send(iq);
 }
 
 void jabber_register_parse(JabberStream *js, xmlnode *packet)
 {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
 	const char *type;
-	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
-		return;
-
-	if(js->registration) {
+	const char *from = xmlnode_get_attrib(packet, "from");
 		PurpleRequestFields *fields;
 		PurpleRequestFieldGroup *group;
 		PurpleRequestField *field;
 		xmlnode *query, *x, *y;
 		char *instructions;
+	JabberRegisterCBData *cbdata;
+	gboolean registered = FALSE;
 
+	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result"))
+		return;
+
+	if(js->registration)
 		/* get rid of the login thingy */
 		purple_connection_set_state(js->gc, PURPLE_CONNECTED);
 
 		query = xmlnode_get_child(packet, "query");
 
 		if(xmlnode_get_child(query, "registered")) {
+		registered = TRUE;
+		
+		if(js->registration) {
 			purple_notify_error(NULL, _("Already Registered"),
 					_("Already Registered"), NULL);
+			if(account->registration_cb)
+				(account->registration_cb)(account, FALSE, account->registration_cb_user_data);
 			jabber_connection_schedule_close(js);
 			return;
 		}
+	}
 
 		if((x = xmlnode_get_child_with_namespace(packet, "x",
 						"jabber:x:data"))) {
-			jabber_x_data_request(js, x, jabber_register_x_data_cb, NULL);
+		jabber_x_data_request(js, x, jabber_register_x_data_cb, g_strdup(from));
 			return;
 		} else if((x = xmlnode_get_child_with_namespace(packet, "x",
 						"jabber:x:oob"))) {
@@ -787,8 +884,12 @@
 				if((href = xmlnode_get_data(url))) {
 					purple_notify_uri(NULL, href);
 					g_free(href);
+				if(js->registration) {
 					js->gc->wants_to_die = TRUE;
+					if(account->registration_cb) /* succeeded, but we have no login info */
+						(account->registration_cb)(account, TRUE, account->registration_cb_user_data);
 					jabber_connection_schedule_close(js);
+				}
 					return;
 				}
 			}
@@ -800,18 +901,32 @@
 		group = purple_request_field_group_new(NULL);
 		purple_request_fields_add_group(fields, group);
 
+	if(js->registration)
 		field = purple_request_field_string_new("username", _("Username"),
 				js->user->node, FALSE);
+	else
+		field = purple_request_field_string_new("username", _("Username"),
+				NULL, FALSE);
+
 		purple_request_field_group_add_field(group, field);
 
+	if(js->registration)
 		field = purple_request_field_string_new("password", _("Password"),
 				purple_connection_get_password(js->gc), FALSE);
+	else
+		field = purple_request_field_string_new("password", _("Password"),
+				NULL, FALSE);
+
 		purple_request_field_string_set_masked(field, TRUE);
 		purple_request_field_group_add_field(group, field);
 
 		if(xmlnode_get_child(query, "name")) {
+		if(js->registration)
 			field = purple_request_field_string_new("name", _("Name"),
 					purple_account_get_alias(js->gc->account), FALSE);
+		else
+			field = purple_request_field_string_new("name", _("Name"),
+					NULL, FALSE);
 			purple_request_field_group_add_field(group, field);
 		}
 		if(xmlnode_get_child(query, "email")) {
@@ -869,23 +984,45 @@
 					NULL, FALSE);
 			purple_request_field_group_add_field(group, field);
 		}
+	if(registered) {
+		field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE);
+		purple_request_field_group_add_field(group, field);
+	}
 
 		if((y = xmlnode_get_child(query, "instructions")))
 			instructions = xmlnode_get_data(y);
+	else if(registered)
+		instructions = g_strdup(_("Please fill out the information below "
+					"to change your account registration."));
 		else
 			instructions = g_strdup(_("Please fill out the information below "
 						"to register your new account."));
 
+	cbdata = g_new0(JabberRegisterCBData, 1);
+	cbdata->js = js;
+	cbdata->who = g_strdup(from);
+
+	if(js->registration)
 		purple_request_fields(js->gc, _("Register New XMPP Account"),
 				_("Register New XMPP Account"), instructions, fields,
 				_("Register"), G_CALLBACK(jabber_register_cb),
 				_("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
 				purple_connection_get_account(js->gc), NULL, NULL,
-				"register-account", js);
+				"register-account", cbdata);
+	else {
+		char *title = registered?g_strdup_printf(_("Change Account Registration at %s"), from)
+								:g_strdup_printf(_("Register New Account at %s"), from);
+		purple_request_fields(js->gc, title,
+			  title, instructions, fields,
+			  registered?_("Change Registration"):_("Register"), G_CALLBACK(jabber_register_cb),
+			  _("Cancel"), G_CALLBACK(jabber_register_cancel_cb),
+			  purple_connection_get_account(js->gc), NULL, NULL,
+			  cbdata);
+		g_free(title);
+	}
 
 		g_free(instructions);
 	}
-}
 
 void jabber_register_start(JabberStream *js)
 {
@@ -895,6 +1032,14 @@
 	jabber_iq_send(iq);
 }
 
+void jabber_register_gateway(JabberStream *js, const char *gateway) {
+	JabberIq *iq;
+	
+	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:register");
+	xmlnode_set_attrib(iq->node, "to", gateway);
+	jabber_iq_send(iq);
+}
+
 void jabber_register_account(PurpleAccount *account)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
@@ -913,6 +1058,7 @@
 			g_free, g_free);
 	js->user = jabber_id_new(purple_account_get_username(account));
 	js->next_id = g_random_int();
+	js->old_length = -1;
 
 	if(!js->user) {
 		purple_connection_error(gc, _("Invalid XMPP ID"));
@@ -966,6 +1112,65 @@
 	}
 }
 
+static void jabber_unregister_account_iq_cb(JabberStream *js, xmlnode *packet, gpointer data) {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	const char *type = xmlnode_get_attrib(packet,"type");
+	if(!strcmp(type,"error")) {
+		char *msg = jabber_parse_error(js, packet);
+		
+		purple_notify_error(js->gc, _("Error unregistering account"),
+							_("Error unregistering account"), msg);
+		g_free(msg);
+		if(js->unregistration_cb)
+			js->unregistration_cb(account, FALSE, js->unregistration_user_data);
+	} else if(!strcmp(type,"result")) {
+		purple_notify_info(js->gc, _("Account successfully unregistered"),
+						   _("Account successfully unregistered"), NULL);
+		if(js->unregistration_cb)
+			js->unregistration_cb(account, TRUE, js->unregistration_user_data);
+	}
+}
+
+static void jabber_unregister_account_cb(JabberStream *js) {
+	JabberIq *iq;
+	xmlnode *query;
+	assert(js->unregistration);
+	
+	iq = jabber_iq_new_query(js,JABBER_IQ_SET,"jabber:iq:register");
+	assert(iq);
+	query = xmlnode_get_child_with_namespace(iq->node,"query","jabber:iq:register");
+	assert(query);
+	xmlnode_new_child(query,"remove");
+	
+	xmlnode_set_attrib(iq->node,"to",js->user->domain);
+	jabber_iq_set_callback(iq,jabber_unregister_account_iq_cb,NULL);
+	
+	jabber_iq_send(iq);
+}
+
+void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data) {
+	PurpleConnection *gc = purple_account_get_connection(account);
+	JabberStream *js;
+	
+	if(gc->state != PURPLE_CONNECTED) {
+		if(gc->state != PURPLE_CONNECTING)
+			jabber_login(account);
+		js = gc->proto_data;
+		js->unregistration = TRUE;
+		js->unregistration_cb = cb;
+		js->unregistration_user_data = user_data;
+		return;
+	}
+	
+	js = gc->proto_data;
+	assert(!js->unregistration); /* don't allow multiple calls */
+	js->unregistration = TRUE;
+	js->unregistration_cb = cb;
+	js->unregistration_user_data = user_data;
+	
+	jabber_unregister_account_cb(js);
+}
+
 void jabber_close(PurpleConnection *gc)
 {
 	JabberStream *js = gc->proto_data;
@@ -993,6 +1198,8 @@
 
 	jabber_buddy_remove_all_pending_buddy_info_requests(js);
 
+	jabber_parser_free(js);
+
 	if(js->iq_callbacks)
 		g_hash_table_destroy(js->iq_callbacks);
 	if(js->disco_callbacks)
@@ -1025,12 +1232,35 @@
 		g_string_free(js->sasl_mechs, TRUE);
 	if(js->sasl_cb)
 		g_free(js->sasl_cb);
+#endif
 	if(js->serverFQDN)
 		g_free(js->serverFQDN);
-#endif
+	while(js->commands) {
+		JabberAdHocCommands *cmd = js->commands->data;
+		g_free(cmd->jid);
+		g_free(cmd->node);
+		g_free(cmd->name);
+		g_free(cmd);
+		js->commands = g_list_delete_link(js->commands, js->commands);
+	}
 	g_free(js->server_name);
 	g_free(js->gmail_last_time);
 	g_free(js->gmail_last_tid);
+	if(js->old_msg)
+		g_free(js->old_msg);
+	if(js->old_avatarhash)
+		g_free(js->old_avatarhash);
+	if(js->old_artist)
+		g_free(js->old_artist);
+	if(js->old_title)
+		g_free(js->old_title);
+	if(js->old_source)
+		g_free(js->old_source);
+	if(js->old_uri)
+		g_free(js->old_uri);
+	if(js->old_track)
+		g_free(js->old_track);
+	
 	g_free(js);
 
 	gc->proto_data = NULL;
@@ -1051,9 +1281,13 @@
 					js->gsc ? 5 : 2, JABBER_CONNECT_STEPS);
 			jabber_stream_init(js);
 			break;
+		case JABBER_STREAM_INITIALIZING_ENCRYPTION:
+			purple_connection_update_progress(js->gc, _("Initializing SSL/TLS"),
+											  6, JABBER_CONNECT_STEPS);
+			break;
 		case JABBER_STREAM_AUTHENTICATING:
 			purple_connection_update_progress(js->gc, _("Authenticating"),
-					js->gsc ? 6 : 3, JABBER_CONNECT_STEPS);
+					js->gsc ? 7 : 3, JABBER_CONNECT_STEPS);
 			if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) {
 				jabber_register_start(js);
 			} else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
@@ -1062,7 +1296,7 @@
 			break;
 		case JABBER_STREAM_REINITIALIZING:
 			purple_connection_update_progress(js->gc, _("Re-initializing Stream"),
-					(js->gsc ? 7 : 4), JABBER_CONNECT_STEPS);
+					(js->gsc ? 8 : 4), JABBER_CONNECT_STEPS);
 
 			/* The stream will be reinitialized later, in jabber_recv_cb_ssl() */
 			js->reinit = TRUE;
@@ -1089,6 +1323,38 @@
 	js->idle = idle ? time(NULL) - idle : idle;
 }
 
+void jabber_add_feature(const char *shortname, const char *namespace, JabberFeatureEnabled cb) {
+	JabberFeature *feat;
+	
+	assert(shortname != NULL);
+	assert(namespace != NULL);
+	
+	feat = g_new0(JabberFeature,1);
+	feat->shortname = g_strdup(shortname);
+	feat->namespace = g_strdup(namespace);
+	feat->is_enabled = cb;
+	
+	/* try to remove just in case it already exists in the list */
+	jabber_remove_feature(shortname);
+	
+	jabber_features = g_list_append(jabber_features, feat);
+}
+
+void jabber_remove_feature(const char *shortname) {
+	GList *feature;
+	for(feature = jabber_features; feature; feature = feature->next) {
+		JabberFeature *feat = (JabberFeature*)feature->data;
+		if(!strcmp(feat->shortname, shortname)) {
+			g_free(feat->shortname);
+			g_free(feat->namespace);
+			
+			g_free(feature->data);
+			feature = g_list_delete_link(feature, feature);
+			break;
+		}
+	}
+}
+
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "jabber";
@@ -1161,8 +1427,12 @@
 		JabberBuddyResource *jbr = NULL;
 		const char *sub;
 		GList *l;
+		const char *mood;
 
 		if (full) {
+			PurpleStatus *status;
+			PurpleValue *value;
+			
 			if(jb->subscription & JABBER_SUB_FROM) {
 				if(jb->subscription & JABBER_SUB_TO)
 					sub = _("Both");
@@ -1180,6 +1450,20 @@
 			}
 			
 			purple_notify_user_info_add_pair(user_info, _("Subscription"), sub);
+			
+			status = purple_presence_get_active_status(purple_buddy_get_presence(b));
+			value = purple_status_get_attr_value(status, "mood");
+			if (value && purple_value_get_type(value) == PURPLE_TYPE_STRING && (mood = purple_value_get_string(value))) {
+				
+				value = purple_status_get_attr_value(status, "moodtext");
+				if(value && purple_value_get_type(value) == PURPLE_TYPE_STRING) {
+					char *moodplustext = g_strdup_printf("%s (%s)",mood,purple_value_get_string(value));
+					
+					purple_notify_user_info_add_pair(user_info, _("Mood"), moodplustext);
+					g_free(moodplustext);
+				} else
+					purple_notify_user_info_add_pair(user_info, _("Mood"), mood);
+		}
 		}
 
 		for(l=jb->resources; l; l = l->next) {
@@ -1242,6 +1526,19 @@
 			NULL, TRUE, TRUE, FALSE,
 			"priority", _("Priority"), priority_value,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1252,6 +1549,19 @@
 			_("Chatty"), TRUE, TRUE, FALSE,
 			"priority", _("Priority"), priority_value,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1262,6 +1572,19 @@
 			NULL, TRUE, TRUE, FALSE,
 			"priority", _("Priority"), priority_value,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1272,6 +1595,19 @@
 			NULL, TRUE, TRUE, FALSE,
 			"priority", _("Priority"), priority_value,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1282,6 +1618,19 @@
 			_("Do Not Disturb"), TRUE, TRUE, FALSE,
 			"priority", _("Priority"), priority_value,
 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
+			"mood", _("Mood"), purple_value_new(PURPLE_TYPE_STRING),
+			"moodtext", _("Mood Text"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_artist", _("Tune Artist"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_title", _("Tune Title"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_album", _("Tune Album"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_genre", _("Tune Genre"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_comment", _("Tune Comment"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_track", _("Tune Track"), purple_value_new(PURPLE_TYPE_STRING),
+			"tune_time", _("Tune Time"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_year", _("Tune Year"), purple_value_new(PURPLE_TYPE_INT),
+			"tune_url", _("Tune URL"), purple_value_new(PURPLE_TYPE_STRING),
+			"nick", _("Nickname"), purple_value_new(PURPLE_TYPE_STRING),
+			"buzz", _("Allow Buzz"), purple_value_new(PURPLE_TYPE_BOOLEAN),
 			NULL);
 	types = g_list_append(types, type);
 
@@ -1388,6 +1737,8 @@
 
 GList *jabber_actions(PurplePlugin *plugin, gpointer context)
 {
+	PurpleConnection *gc = (PurpleConnection *) context;
+	JabberStream *js = gc->proto_data;
 	GList *m = NULL;
 	PurplePluginAction *act;
 
@@ -1405,6 +1756,14 @@
 	                             jabber_user_search_begin);
 	m = g_list_append(m, act);
 
+	purple_debug_info("jabber", "jabber_actions: have pep: %s\n", js->pep?"YES":"NO");
+
+	if(js->pep)
+		jabber_pep_init_actions(&m);
+	
+	if(js->commands)
+		jabber_adhoc_init_server_commands(js, &m);
+
 	return m;
 }
 
@@ -1809,6 +2168,71 @@
 	return PURPLE_CMD_RET_OK;
 }
 
+static PurpleCmdRet jabber_cmd_ping(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	if(!jabber_ping_jid(conv, args[0])) {
+		*error = g_strdup_printf(_("Unable to ping user %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet jabber_cmd_buzz(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	JabberStream *js = conv->account->gc->proto_data;
+	xmlnode *msg, *buzz;
+	JabberBuddy *jb;
+	JabberBuddyResource *jbr;
+	char *to;
+	GList *iter;
+
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+	
+	jb = jabber_buddy_find(js, args[0], FALSE);
+	if(!jb) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	
+	jbr = jabber_buddy_find_resource(jb, NULL);
+	if(!jbr) {
+		*error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	if(!jbr->caps) {
+		*error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) {
+		if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+			msg = xmlnode_new("message");
+			to = g_strdup_printf("%s/%s", args[0], jbr->name);
+			xmlnode_set_attrib(msg,"to",to);
+			g_free(to);
+			
+			/* avoid offline storage */
+			xmlnode_set_attrib(msg,"type","headline");
+			
+			buzz = xmlnode_new_child(msg,"attention");
+			xmlnode_set_namespace(buzz,"http://www.xmpp.org/extensions/xep-0224.html#ns");
+			
+			jabber_send(js,msg);
+			xmlnode_free(msg);
+			
+			return PURPLE_CMD_RET_OK;
+		}
+	}
+	*error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), args[0]);
+	return PURPLE_CMD_RET_FAILED;
+}
+
 gboolean jabber_offline_message(const PurpleBuddy *buddy)
 {
 	return TRUE;
@@ -1886,6 +2310,16 @@
 	                  "prpl-jabber", jabber_cmd_chat_msg,
 	                  _("msg &lt;user&gt; &lt;message&gt;:  Send a private message to another user."),
 	                  NULL);
+	purple_cmd_register("ping", "w", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM |
+					  PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_ping,
+					  _("ping &lt;jid&gt;:	Ping a user/component/server."),
+					  NULL);
+	purple_cmd_register("buzz", "s", PURPLE_CMD_P_PRPL,
+					  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
+					  "prpl-jabber", jabber_cmd_buzz,
+					  _("buzz: Buzz a user to get their attention"), NULL);
 }
 
 void
--- a/libpurple/protocols/jabber/jabber.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,28 +17,11 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_H_
 #define _PURPLE_JABBER_H_
 
-#include <libxml/parser.h>
-#include <glib.h>
-#include "circbuffer.h"
-#include "connection.h"
-#include "dnssrv.h"
-#include "roomlist.h"
-#include "sslconn.h"
-
-#include "jutil.h"
-#include "xmlnode.h"
-
-#ifdef HAVE_CYRUS_SASL
-#include <sasl/sasl.h>
-#endif
-
-#define CAPS0115_NODE "http://pidgin.im/caps"
-
 typedef enum {
 	JABBER_CAP_NONE           = 0,
 	JABBER_CAP_XHTML          = 1 << 0,
@@ -51,25 +34,49 @@
 	JABBER_CAP_IQ_SEARCH      = 1 << 7,
 	JABBER_CAP_IQ_REGISTER    = 1 << 8,
 
-	/* Google Talk extensions: 
+	/* Google Talk extensions:
 	 * http://code.google.com/apis/talk/jep_extensions/extensions.html
 	 */
 	JABBER_CAP_GMAIL_NOTIFY   = 1 << 9,
 	JABBER_CAP_GOOGLE_ROSTER  = 1 << 10,
 
+	JABBER_CAP_PING			  = 1 << 11,
+	JABBER_CAP_ADHOC		  = 1 << 12,
+	
 	JABBER_CAP_RETRIEVED      = 1 << 31
 } JabberCapabilities;
 
+typedef struct _JabberStream JabberStream;
+
+#include <libxml/parser.h>
+#include <glib.h>
+#include "circbuffer.h"
+#include "connection.h"
+#include "dnssrv.h"
+#include "roomlist.h"
+#include "sslconn.h"
+
+#include "jutil.h"
+#include "xmlnode.h"
+#include "buddy.h"
+
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
+#define CAPS0115_NODE "http://pidgin.im/caps"
+
 typedef enum {
 	JABBER_STREAM_OFFLINE,
 	JABBER_STREAM_CONNECTING,
 	JABBER_STREAM_INITIALIZING,
+	JABBER_STREAM_INITIALIZING_ENCRYPTION,
 	JABBER_STREAM_AUTHENTICATING,
 	JABBER_STREAM_REINITIALIZING,
 	JABBER_STREAM_CONNECTED
 } JabberStreamState;
 
-typedef struct _JabberStream
+struct _JabberStream
 {
 	int fd;
 
@@ -136,6 +143,8 @@
 	char *gmail_last_time;
 	char *gmail_last_tid;
 
+    char *serverFQDN;
+
 	/* OK, this stays at the end of the struct, so plugins can depend
 	 * on the rest of the stuff being in the right place
 	 */
@@ -150,13 +159,50 @@
 	int sasl_state;
 	int sasl_maxbuf;
 	GString *sasl_mechs;
-	char *serverFQDN;
 
+	gboolean unregistration;
+	PurpleAccountUnregistrationCb unregistration_cb;
+	void *unregistration_user_data;
+	
 	gboolean vcard_fetched;
 
-} JabberStream;
+	/* does the local server support PEP? */
+	gboolean pep;
 
-void jabber_process_packet(JabberStream *js, xmlnode *packet);
+	/* Is Buzz enabled? */
+	gboolean allowBuzz;
+	
+	/* A list of JabberAdHocCommands supported by the server */
+	GList *commands;
+	
+	/* last presence update to check for differences */
+	JabberBuddyState old_state;
+	char *old_msg;
+	int old_priority;
+	char *old_avatarhash;
+	
+	/* same for user tune */
+	char *old_artist;
+	char *old_title;
+	char *old_source;
+	char *old_uri;
+	int old_length;
+	char *old_track;
+};
+
+typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
+typedef struct _JabberFeature
+{
+	gchar *shortname;
+	gchar *namespace;
+	JabberFeatureEnabled *is_enabled;
+} JabberFeature;
+
+/* what kind of additional features as returned from disco#info are supported? */
+extern GList *jabber_features;
+
+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);
 
@@ -169,6 +215,9 @@
 
 char *jabber_parse_error(JabberStream *js, xmlnode *packet);
 
+void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */
+void jabber_remove_feature(const gchar *shortname);
+
 /** PRPL functions */
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b);
 const char* jabber_list_emblem(PurpleBuddy *b);
@@ -179,7 +228,9 @@
 void jabber_close(PurpleConnection *gc);
 void jabber_idle_set(PurpleConnection *gc, int idle);
 void jabber_keepalive(PurpleConnection *gc);
+void jabber_register_gateway(JabberStream *js, const char *gateway);
 void jabber_register_account(PurpleAccount *account);
+void jabber_unregister_account(PurpleAccount *account, PurpleAccountUnregistrationCb cb, void *user_data);
 void jabber_convo_closed(PurpleConnection *gc, const char *who);
 PurpleChat *jabber_find_blist_chat(PurpleAccount *account, const char *name);
 gboolean jabber_offline_message(const PurpleBuddy *buddy);
--- a/libpurple/protocols/jabber/jutil.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
--- a/libpurple/protocols/jabber/jutil.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/jutil.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,11 +17,13 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_JUTIL_H_
 #define _PURPLE_JABBER_JUTIL_H_
 
+#include "account.h"
+
 typedef struct _JabberID {
 	char *node;
 	char *domain;
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -39,6 +39,9 @@
 #include "message.h"
 #include "presence.h"
 #include "google.h"
+#include "pep.h"
+#include "usertune.h"
+#include "caps.h"
 
 static PurplePluginProtocolInfo prpl_info =
 {
@@ -52,7 +55,7 @@
 #endif
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
-	{"png,gif,jpeg", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+	{"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	jabber_list_icon,				/* list_icon */
 	jabber_list_emblem,			/* list_emblems */
 	jabber_status_text,				/* status_text */
@@ -111,11 +114,11 @@
 	NULL,							/* whiteboard_prpl_ops */
 	jabber_prpl_send_raw,			/* send_raw */
 	jabber_roomlist_room_serialize, /* roomlist_room_serialize */
+	jabber_unregister_account,		/* unregister_user */
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
@@ -191,49 +194,63 @@
 static void
 init_plugin(PurplePlugin *plugin)
 {
-        PurpleAccountUserSplit *split;
-        PurpleAccountOption *option;
-
+	PurpleAccountUserSplit *split;
+	PurpleAccountOption *option;
+	
 	/* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */
-        split = purple_account_user_split_new(_("Domain"), NULL, '@');
-		purple_account_user_split_set_reverse(split, FALSE);
-        prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
-
-        split = purple_account_user_split_new(_("Resource"), "Home", '/');
-		purple_account_user_split_set_reverse(split, FALSE);
-        prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
-
-        option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-        option = purple_account_option_bool_new(
-                        _("Allow plaintext auth over unencrypted streams"),
-                        "auth_plain_in_clear", FALSE);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
+	split = purple_account_user_split_new(_("Domain"), NULL, '@');
+	purple_account_user_split_set_reverse(split, FALSE);
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+	
+	split = purple_account_user_split_new(_("Resource"), "Home", '/');
+	purple_account_user_split_set_reverse(split, FALSE);
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+	
+	option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_bool_new(
+											_("Allow plaintext auth over unencrypted streams"),
+											"auth_plain_in_clear", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_int_new(_("Connect port"), "port", 5222);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	option = purple_account_option_string_new(_("Connect server"),
+											  "connect_server", NULL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+	
+	
+	jabber_init_plugin(plugin);
+	
+	purple_prefs_remove("/plugins/prpl/jabber");
+	
+	/* XXX - If any other plugin wants SASL this won't be good ... */
+#ifdef HAVE_CYRUS_SASL
+	sasl_client_init(NULL);
+#endif
+	jabber_register_commands();
+	
+	jabber_iq_init();
+	jabber_pep_init();
+	
+	jabber_tune_init();
+	jabber_caps_init();
 
-        option = purple_account_option_int_new(_("Connect port"), "port", 5222);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-        option = purple_account_option_string_new(_("Connect server"),
-                        "connect_server", NULL);
-        prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
-                        option);
-
-
-        jabber_init_plugin(plugin);
-
-        purple_prefs_remove("/plugins/prpl/jabber");
-
-        /* XXX - If any other plugin wants SASL this won't be good ... */
-#ifdef HAVE_CYRUS_SASL
-        sasl_client_init(NULL);
-#endif
-        jabber_register_commands();
-
-        jabber_iq_init();
+	jabber_add_feature("avatarmeta", AVATARNAMESPACEMETA, jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_add_feature("avatardata", AVATARNAMESPACEDATA, jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_add_feature("buzz", "http://www.xmpp.org/extensions/xep-0224.html#ns", jabber_buzz_isenabled);
+	
+	jabber_pep_register_handler("avatar", AVATARNAMESPACEMETA, jabber_buddy_avatar_update_metadata);
 }
 
 
--- a/libpurple/protocols/jabber/message.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/message.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -30,6 +30,7 @@
 #include "google.h"
 #include "message.h"
 #include "xmlnode.h"
+#include "pep.h"
 
 void jabber_message_free(JabberMessage *jm)
 {
@@ -147,9 +148,13 @@
 static void handle_headline(JabberMessage *jm)
 {
 	char *title;
-	GString *body = g_string_new("");
+	GString *body;
 	GList *etc;
 
+	if(!jm->xhtml && !jm->body)
+		return; /* ignore headlines without any content */
+
+	body = g_string_new("");
 	title = g_strdup_printf(_("Message from %s"), jm->from);
 
 	if(jm->xhtml)
@@ -273,6 +278,36 @@
 	g_free(buf);
 }
 
+static void handle_buzz(JabberMessage *jm) {
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+	PurpleConversation *c;
+	char *username, *str;
+	
+	/* Delayed buzz MUST NOT be accepted */
+	if(jm->delayed)
+		return;
+	
+	/* Reject buzz when it's not enabled */
+	if(!jm->js->allowBuzz)
+		return;
+	
+	account = purple_connection_get_account(jm->js->gc);
+	
+	if ((buddy = purple_find_buddy(account, jm->from)) != NULL)
+		username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1);
+	else
+		return; /* Do not accept buzzes from unknown people */
+
+	c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, jm->from);
+
+	str = g_strdup_printf(_("%s has buzzed you!"), username);
+	
+	purple_conversation_write(c, NULL, str, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
+	g_free(username);
+	g_free(str);
+}
+
 void jabber_message_parse(JabberStream *js, xmlnode *packet)
 {
 	JabberMessage *jm;
@@ -308,22 +343,25 @@
 	jm->id = g_strdup(xmlnode_get_attrib(packet, "id"));
 
 	for(child = packet->child; child; child = child->next) {
+		const char *xmlns = xmlnode_get_namespace(child);
+		if(!xmlns)
+			xmlns = "";
 		if(child->type != XMLNODE_TYPE_TAG)
 			continue;
 
-		if(!strcmp(child->name, "subject")) {
+		if(!strcmp(child->name, "subject") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->subject)
 				jm->subject = xmlnode_get_data(child);
-		} else if(!strcmp(child->name, "thread")) {
+		} else if(!strcmp(child->name, "thread") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->thread_id)
 				jm->thread_id = xmlnode_get_data(child);
-		} else if(!strcmp(child->name, "body")) {
+		} else if(!strcmp(child->name, "body") && !strcmp(xmlns,"jabber:client")) {
 			if(!jm->body) {
 				char *msg = xmlnode_to_str(child, NULL);
 				jm->body = purple_strdup_withhtml(msg);
 				g_free(msg);
 			}
-		} else if(!strcmp(child->name, "html")) {
+		} else if(!strcmp(child->name, "html") && !strcmp(xmlns,"http://jabber.org/protocol/xhtml-im")) {
 			if(!jm->xhtml && xmlnode_get_child(child, "body")) {
 				char *c;
 				jm->xhtml = xmlnode_to_str(child, NULL);
@@ -335,21 +373,28 @@
 						*c = ' ';
 				}
 			}
-		} else if(!strcmp(child->name, "active")) {
+		} else if(!strcmp(child->name, "active") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_ACTIVE;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "composing")) {
+		} else if(!strcmp(child->name, "composing") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_COMPOSING;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "paused")) {
+		} else if(!strcmp(child->name, "paused") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_PAUSED;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "inactive")) {
+		} else if(!strcmp(child->name, "inactive") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_INACTIVE;
 			jm->typing_style |= JM_TS_JEP_0085;
-		} else if(!strcmp(child->name, "gone")) {
+		} else if(!strcmp(child->name, "gone") && !strcmp(xmlns,"http://jabber.org/protocol/chatstates")) {
 			jm->chat_state = JM_STATE_GONE;
 			jm->typing_style |= JM_TS_JEP_0085;
+		} else if(!strcmp(child->name, "event") && !strcmp(xmlns,"http://jabber.org/protocol/pubsub#event")) {
+			xmlnode *items;
+			jm->type = JABBER_MESSAGE_EVENT;
+			for(items = xmlnode_get_child(child,"items"); items; items = items->next)
+				jm->eventitems = g_list_append(jm->eventitems, items);
+		} else if(!strcmp(child->name, "attention") && !strcmp(xmlns,"http://www.xmpp.org/extensions/xep-0224.html#ns")) {
+			jm->hasBuzz = TRUE;
 		} else if(!strcmp(child->name, "error")) {
 			const char *code = xmlnode_get_attrib(child, "code");
 			char *code_txt = NULL;
@@ -364,8 +409,12 @@
 
 			g_free(code_txt);
 			g_free(text);
+		} else if(!strcmp(child->name, "delay") && xmlns && !strcmp(xmlns,"urn:xmpp:delay")) {
+			const char *timestamp = xmlnode_get_attrib(child, "stamp");
+			jm->delayed = TRUE;
+			if(timestamp)
+				jm->sent = purple_str_to_time(timestamp, TRUE, NULL, NULL, NULL);
 		} else if(!strcmp(child->name, "x")) {
-			const char *xmlns = xmlnode_get_namespace(child);
 			if(xmlns && !strcmp(xmlns, "jabber:x:event")) {
 				if(xmlnode_get_child(child, "composing")) {
 					if(jm->chat_state == JM_STATE_ACTIVE)
@@ -411,6 +460,9 @@
 		}
 	}
 
+	if(jm->hasBuzz)
+		handle_buzz(jm);
+
 	switch(jm->type) {
 		case JABBER_MESSAGE_NORMAL:
 		case JABBER_MESSAGE_CHAT:
@@ -425,6 +477,9 @@
 		case JABBER_MESSAGE_GROUPCHAT_INVITE:
 			handle_groupchat_invite(jm);
 			break;
+		case JABBER_MESSAGE_EVENT:
+			jabber_handle_event(jm);
+			break;
 		case JABBER_MESSAGE_ERROR:
 			handle_error(jm);
 			break;
@@ -461,6 +516,7 @@
 			type = "error";
 			break;
 		case JABBER_MESSAGE_OTHER:
+		default:
 			type = NULL;
 			break;
 	}
@@ -689,3 +745,8 @@
 	jabber_message_send(jm);
 	jabber_message_free(jm);
 }
+
+gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace) {
+	return js->allowBuzz;
+}
+
--- a/libpurple/protocols/jabber/message.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/message.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_MESSAGE_H_
 #define _PURPLE_JABBER_MESSAGE_H_
@@ -35,10 +35,12 @@
 		JABBER_MESSAGE_HEADLINE,
 		JABBER_MESSAGE_ERROR,
 		JABBER_MESSAGE_GROUPCHAT_INVITE,
+		JABBER_MESSAGE_EVENT,
 		JABBER_MESSAGE_OTHER
 	} type;
 	time_t sent;
 	gboolean delayed;
+	gboolean hasBuzz;
 	char *id;
 	char *from;
 	char *to;
@@ -61,6 +63,7 @@
 		JM_STATE_GONE
 	} chat_state;
 	GList *etc;
+	GList *eventitems;
 } JabberMessage;
 
 void jabber_message_free(JabberMessage *jm);
@@ -75,4 +78,6 @@
 unsigned int jabber_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state);
 void jabber_message_conv_closed(JabberStream *js, const char *who);
 
+gboolean jabber_buzz_isenabled(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
 #endif /* _PURPLE_JABBER_MESSAGE_H_ */
--- a/libpurple/protocols/jabber/oob.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/oob.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
--- a/libpurple/protocols/jabber/oob.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/oob.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_OOB_H_
 #define _PURPLE_JABBER_OOB_H_
--- a/libpurple/protocols/jabber/parser.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/parser.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -63,7 +63,7 @@
 		if(js->protocol_version == JABBER_PROTO_0_9)
 			js->auth_type = JABBER_AUTH_IQ_AUTH;
 
-		if(js->state == JABBER_STREAM_INITIALIZING)
+		if(js->state == JABBER_STREAM_INITIALIZING || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION)
 			jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING);
 	} else {
 
@@ -113,7 +113,7 @@
 	} else {
 		xmlnode *packet = js->current;
 		js->current = NULL;
-		jabber_process_packet(js, packet);
+		jabber_process_packet(js, &packet);
 		xmlnode_free(packet);
 	}
 }
@@ -174,6 +174,10 @@
 	 * the parser context when you try to use it (this way, it can figure
 	 * out the encoding at creation time. So, setting up the parser is
 	 * just a matter of destroying any current parser. */
+	jabber_parser_free(js);
+}
+
+void jabber_parser_free(JabberStream *js) {
 	if (js->context) {
 		xmlParseChunk(js->context, NULL,0,1);
 		xmlFreeParserCtxt(js->context);
@@ -181,7 +185,6 @@
 	}
 }
 
-
 void jabber_parser_process(JabberStream *js, const char *buf, int len)
 {
 	if (js->context ==  NULL) {
--- a/libpurple/protocols/jabber/parser.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/parser.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_PARSER_H_
 #define _PURPLE_JABBER_PARSER_H_
@@ -25,6 +25,7 @@
 #include "jabber.h"
 
 void jabber_parser_setup(JabberStream *js);
+void jabber_parser_free(JabberStream *js);
 void jabber_parser_process(JabberStream *js, const char *buf, int len);
 
 #endif /* _PURPLE_JABBER_PARSER_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/pep.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,130 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "pep.h"
+#include "iq.h"
+#include <string.h>
+#include "usermood.h"
+#include "usernick.h"
+
+static GHashTable *pep_handlers = NULL;
+
+void jabber_pep_init(void) {
+	if(!pep_handlers) {
+		pep_handlers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+		
+		/* register PEP handlers */
+		jabber_mood_init();
+		jabber_nick_init();
+	}
+}
+
+void jabber_pep_init_actions(GList **m) {
+	/* register the PEP-specific actions */
+	jabber_mood_init_action(m);
+	jabber_nick_init_action(m);
+}
+
+void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc) {
+	gchar *notifyns = g_strdup_printf("%s+notify", xmlns);
+	jabber_add_feature(shortname, notifyns, NULL); /* receiving PEPs is always supported */
+	g_free(notifyns);
+	g_hash_table_replace(pep_handlers, g_strdup(xmlns), handlerfunc);
+}
+
+static void do_pep_iq_request_item_callback(JabberStream *js, xmlnode *packet, gpointer data) {
+	const char *from = xmlnode_get_attrib(packet,"from");
+	xmlnode *pubsub = xmlnode_get_child_with_namespace(packet,"pubsub","http://jabber.org/protocol/pubsub#event");
+	xmlnode *items = NULL;
+	JabberPEPHandler *cb = data;
+	
+	if(pubsub)
+		items = xmlnode_get_child(pubsub, "items");
+	
+	cb(js, from, items);
+}
+
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb) {
+	JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET);
+	xmlnode *pubsub, *items, *item;
+	
+	xmlnode_set_attrib(iq->node,"to",to);
+	pubsub = xmlnode_new_child(iq->node,"pubsub");
+	
+	xmlnode_set_namespace(pubsub,"http://jabber.org/protocol/pubsub");
+	
+	items = xmlnode_new_child(pubsub, "items");
+	xmlnode_set_attrib(items,"node",node);
+	
+	item = xmlnode_new_child(items, "item");
+	if(id)
+		xmlnode_set_attrib(item, "id", id);
+	
+	jabber_iq_set_callback(iq,do_pep_iq_request_item_callback,(gpointer)cb);
+	
+	jabber_iq_send(iq);
+}
+
+gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace) {
+	return js->pep;
+}
+
+void jabber_handle_event(JabberMessage *jm) {
+	/* this may be called even when the own server doesn't support pep! */
+	JabberPEPHandler *jph;
+	GList *itemslist;
+	char *jid = jabber_get_bare_jid(jm->from);
+	
+	for(itemslist = jm->eventitems; itemslist; itemslist = itemslist->next) {
+		xmlnode *items = (xmlnode*)itemslist->data;
+		const char *nodename = xmlnode_get_attrib(items,"node");
+		
+		if(nodename && (jph = g_hash_table_lookup(pep_handlers, nodename)))
+			jph(jm->js, jid, items);
+	}
+	
+	/* discard items we don't have a handler for */
+	g_free(jid);
+}
+
+void jabber_pep_publish(JabberStream *js, xmlnode *publish) {
+	JabberIq *iq;
+	xmlnode *pubsub;
+
+	if(js->pep != TRUE) {
+		/* ignore when there's no PEP support on the server */
+		xmlnode_free(publish);
+		return;
+	}
+
+	iq = jabber_iq_new(js, JABBER_IQ_SET);
+
+	pubsub = xmlnode_new("pubsub");
+	xmlnode_set_namespace(pubsub, "http://jabber.org/protocol/pubsub");
+
+	xmlnode_insert_child(pubsub, publish);
+
+	xmlnode_insert_child(iq->node, pubsub);
+
+	jabber_iq_send(iq);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/pep.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,85 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_PEP_H_
+#define _PURPLE_JABBER_PEP_H_
+
+#include "jabber.h"
+#include "message.h"
+#include "buddy.h"
+
+void jabber_pep_init(void);
+
+void jabber_pep_init_actions(GList **m);
+
+/*
+ * Callback for receiving PEP events.
+ *
+ * @parameter js	The JabberStream this item was received on
+ * @parameter items	The &lt;items/>-tag with the &lt;item/>-children
+ */
+typedef void (JabberPEPHandler)(JabberStream *js, const char *from, xmlnode *items);
+
+/*
+ * Registers a callback for PEP events. Also automatically announces this receiving capability via disco#info.
+ * Don't forget to use jabber_add_feature when supporting the sending of PEP events of this type.
+ *
+ * @parameter shortname		A short name for this feature for XEP-0115. It has no semantic meaning, it just has to be unique.
+ * @parameter xmlns		The namespace for this event
+ * @parameter handlerfunc	The callback to be used when receiving an event with this namespace
+ */
+void jabber_pep_register_handler(const char *shortname, const char *xmlns, JabberPEPHandler handlerfunc);
+
+/*
+ * Request a specific item from another PEP node.
+ *
+ * @parameter js	The JabberStream that should be used
+ * @parameter to	The target PEP node
+ * @parameter node	The node name of the item that is requested
+ * @parameter id	The item id of the requested item (may be NULL)
+ * @parameter cb	The callback to be used when this item is received
+ *
+ * The items element passed to the callback will be NULL if any error occured (like a permission error, node doesn't exist etc.)
+ */
+void jabber_pep_request_item(JabberStream *js, const char *to, const char *node, const char *id, JabberPEPHandler cb);
+
+/*
+ * Default callback that can be used for namespaces which should only be enabled when PEP is supported
+ *
+ * @parameter js	The JabberStream struct for this connection
+ * @parameter shortname	The namespace's shortname (for caps), ignored.
+ * @parameter namespace The namespace that's queried, ignored.
+ *
+ * @returns TRUE when PEP is enabled, FALSE otherwise
+ */
+gboolean jabber_pep_namespace_only_when_pep_enabled_cb(JabberStream *js, const gchar *shortname, const gchar *namespace);
+
+void jabber_handle_event(JabberMessage *jm);
+
+/*
+ * Publishes PEP item(s)
+ *
+ * @parameter js	The JabberStream associated with the connection this event should be published
+ * @parameter publish	The publish node. This could be for example &lt;publish node='http://jabber.org/protocol/tune'/> with an &lt;item/> as subnode
+ */
+void jabber_pep_publish(JabberStream *js, xmlnode *publish);
+
+#endif /* _PURPLE_JABBER_PEP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ping.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,80 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "internal.h"
+
+#include "debug.h"
+#include "xmlnode.h"
+
+#include "jabber.h"
+#include "ping.h"
+#include "iq.h"
+
+void
+jabber_ping_parse(JabberStream *js, xmlnode *packet)
+{
+	JabberIq *iq;
+
+	purple_debug_info("jabber", "jabber_ping_parse\n");
+
+	iq = jabber_iq_new(js, JABBER_IQ_RESULT);
+
+	xmlnode_set_attrib(iq->node, "to", xmlnode_get_attrib(packet, "from") );
+
+	jabber_iq_set_id(iq, xmlnode_get_attrib(packet, "id"));
+
+	jabber_iq_send(iq);
+}
+
+static void jabber_ping_result_cb(JabberStream *js, xmlnode *packet,
+		gpointer data)
+{
+	const char *type = xmlnode_get_attrib(packet, "type");
+
+	purple_debug_info("jabber", "jabber_ping_result_cb\n");
+	if(type && !strcmp(type, "result")) {
+		purple_debug_info("jabber", "PONG!\n");
+	} else {
+		purple_debug_info("jabber", "(not supported)\n");
+	}
+}
+
+gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid)
+{
+	JabberIq *iq;
+	xmlnode *ping;
+
+	purple_debug_info("jabber", "jabber_ping_jid\n");
+
+	iq = jabber_iq_new(conv->account->gc->proto_data, JABBER_IQ_GET);
+	xmlnode_set_attrib(iq->node, "to", jid);
+
+	ping = xmlnode_new_child(iq->node, "ping");
+	xmlnode_set_namespace(ping, "urn:xmpp:ping");
+
+	jabber_iq_set_callback(iq, jabber_ping_result_cb, NULL);
+	jabber_iq_send(iq);
+
+
+
+	return TRUE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/ping.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,35 @@
+/**
+ * @file ping.h utility functions
+ *
+ * purple
+ *
+ * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _PURPLE_JABBER_PING_H_
+#define _PURPLE_JABBER_PING_H_
+
+#include "jabber.h"
+#include "conversation.h"
+
+void jabber_ping_parse(JabberStream *js,
+						xmlnode *packet);
+
+
+gboolean jabber_ping_jid(PurpleConversation *conv, const char *jid);
+
+
+#endif /* _PURPLE_JABBER_PING_H_ */
--- a/libpurple/protocols/jabber/presence.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -36,6 +36,9 @@
 #include "presence.h"
 #include "iq.h"
 #include "jutil.h"
+#include "adhoccommands.h"
+
+#include "usertune.h"
 
 
 static void chats_send_presence_foreach(gpointer key, gpointer val,
@@ -101,6 +104,9 @@
 	char *stripped = NULL;
 	JabberBuddyState state;
 	int priority;
+	const char *artist, *title, *source, *uri, *track;
+	int length;
+	gboolean allowBuzz;
 
 	if(NULL == status) {
 		PurplePresence *gpresence = purple_account_get_presence(account);
@@ -127,28 +133,95 @@
 	}
 
 	purple_status_to_jabber(status, &state, &stripped, &priority);
-
+	
+	/* check for buzz support */
+	allowBuzz = purple_status_get_attr_boolean(status,"buzz");
+	/* changing the buzz state has to trigger a re-broadcasting of the presence for caps */
+	
+#define CHANGED(a,b) ((!a && b) || (a && a[0] == '\0' && b && b[0] != '\0') || \
+					  (a && !b) || (a && a[0] != '\0' && b && b[0] == '\0') || (a && b && strcmp(a,b)))
+	/* check if there are any differences to the <presence> and send them in that case */
+	if (allowBuzz != js->allowBuzz || js->old_state != state || CHANGED(js->old_msg, stripped) ||
+		js->old_priority != priority || CHANGED(js->old_avatarhash, js->avatar_hash)) {
+		js->allowBuzz = allowBuzz;
+		presence = jabber_presence_create_js(js, state, stripped, priority);
 
-	presence = jabber_presence_create(state, stripped, priority);
-	g_free(stripped);
+		if(js->avatar_hash) {
+			x = xmlnode_new_child(presence, "x");
+			xmlnode_set_namespace(x, "vcard-temp:x:update");
+			photo = xmlnode_new_child(x, "photo");
+			xmlnode_insert_data(photo, js->avatar_hash, -1);
+		}
+
+		jabber_send(js, presence);
 
-	if(js->avatar_hash) {
-		x = xmlnode_new_child(presence, "x");
-		xmlnode_set_namespace(x, "vcard-temp:x:update");
-		photo = xmlnode_new_child(x, "photo");
-		xmlnode_insert_data(photo, js->avatar_hash, -1);
+		g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
+		xmlnode_free(presence);
+		
+		/* update old values */
+		
+		if(js->old_msg)
+			g_free(js->old_msg);
+		if(js->old_avatarhash)
+			g_free(js->old_avatarhash);
+		js->old_msg = g_strdup(stripped);
+		js->old_avatarhash = g_strdup(js->avatar_hash);
+		js->old_state = state;
+		js->old_priority = priority;
+		g_free(stripped);
+	}
+					  	
+	/* next, check if there are any changes to the tune values */
+	artist = purple_status_get_attr_string(status, PURPLE_TUNE_ARTIST);
+	title = purple_status_get_attr_string(status, PURPLE_TUNE_TITLE);
+	source = purple_status_get_attr_string(status, PURPLE_TUNE_ALBUM);
+	uri = purple_status_get_attr_string(status, PURPLE_TUNE_URL);
+	track = purple_status_get_attr_string(status, PURPLE_TUNE_TRACK);
+	length = (!purple_status_get_attr_value(status, PURPLE_TUNE_TIME))?-1:purple_status_get_attr_int(status, PURPLE_TUNE_TIME);
+	
+	if(CHANGED(artist, js->old_artist) || CHANGED(title, js->old_title) || CHANGED(source, js->old_source) ||
+	   CHANGED(uri, js->old_uri) || CHANGED(track, js->old_track) || (length != js->old_length)) {
+		PurpleJabberTuneInfo tuneinfo = {
+			(char*)artist,
+			(char*)title,
+			(char*)source,
+			(char*)track,
+			length,
+			(char*)uri
+		};
+		jabber_tune_set(js->gc, &tuneinfo);
+		
+		/* update old values */
+		if(js->old_artist)
+			g_free(js->old_artist);
+		if(js->old_title)
+			g_free(js->old_title);
+		if(js->old_source)
+			g_free(js->old_source);
+		if(js->old_uri)
+			g_free(js->old_uri);
+		if(js->old_track)
+			g_free(js->old_track);
+		js->old_artist = g_strdup(artist);
+		js->old_title = g_strdup(title);
+		js->old_source = g_strdup(source);
+		js->old_uri = g_strdup(uri);
+		js->old_length = length;
+		js->old_track = g_strdup(track);
 	}
 
-	jabber_send(js, presence);
-
-	g_hash_table_foreach(js->chats, chats_send_presence_foreach, presence);
-	xmlnode_free(presence);
+#undef CHANGED
 
 	jabber_presence_fake_to_self(js, status);
 }
 
 xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority)
 {
+    return jabber_presence_create_js(NULL, state, msg, priority);
+}
+
+xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority)
+{
 	xmlnode *show, *status, *presence, *pri, *c;
 	const char *show_string = NULL;
 
@@ -183,7 +256,39 @@
 	xmlnode_set_namespace(c, "http://jabber.org/protocol/caps");
 	xmlnode_set_attrib(c, "node", CAPS0115_NODE);
 	xmlnode_set_attrib(c, "ver", VERSION);
-
+	
+	if(js != NULL) {
+		/* add the extensions */
+		char extlist[1024];
+		unsigned remaining = 1023; /* one less for the \0 */
+		GList *feature;
+		
+		extlist[0] = '\0';
+		for(feature = jabber_features; feature && remaining > 0; feature = feature->next) {
+			JabberFeature *feat = (JabberFeature*)feature->data;
+			unsigned featlen;
+			
+			if(feat->is_enabled != NULL && feat->is_enabled(js, feat->shortname, feat->namespace) == FALSE)
+				continue; /* skip this feature */
+			
+			featlen = strlen(feat->shortname);
+			
+			/* cut off when we don't have any more space left in our buffer (too bad) */
+			if(featlen > remaining)
+				break;
+			
+			strncat(extlist,feat->shortname,remaining);
+			remaining -= featlen;
+			if(feature->next) { /* no space at the end */
+				strncat(extlist," ",remaining);
+				--remaining;
+			}
+		}
+		/* did we add anything? */
+		if(remaining < 1023)
+			xmlnode_set_attrib(c, "ext", extlist);
+	}
+	
 	return presence;
 }
 
@@ -252,6 +357,35 @@
 	}
 }
 
+typedef struct _JabberPresenceCapabilities {
+	JabberStream *js;
+	JabberBuddyResource *jbr;
+	char *from;
+} JabberPresenceCapabilities;
+
+static void jabber_presence_set_capabilities(JabberCapsClientInfo *info, gpointer user_data) {
+	JabberPresenceCapabilities *userdata = user_data;
+	GList *iter;
+	
+	if(userdata->jbr->caps)
+		jabber_caps_free_clientinfo(userdata->jbr->caps);
+	userdata->jbr->caps = info;
+	
+	for(iter = info->features; iter; iter = g_list_next(iter)) {
+		if(!strcmp((const char*)iter->data, "http://jabber.org/protocol/commands")) {
+			JabberIq *iq = jabber_iq_new_query(userdata->js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+			xmlnode *query = xmlnode_get_child_with_namespace(iq->node,"query","http://jabber.org/protocol/disco#items");
+			xmlnode_set_attrib(iq->node, "to", userdata->from);
+			xmlnode_set_attrib(query, "node", "http://jabber.org/protocol/commands");
+			
+			jabber_iq_set_callback(iq, jabber_adhoc_disco_result_cb, NULL);
+			jabber_iq_send(iq);
+			break;
+		}
+	}
+	g_free(user_data);
+}
+
 void jabber_presence_parse(JabberStream *js, xmlnode *packet)
 {
 	const char *from = xmlnode_get_attrib(packet, "from");
@@ -273,6 +407,7 @@
 	xmlnode *y;
 	gboolean muc = FALSE;
 	char *avatar_hash = NULL;
+	xmlnode *caps = NULL;
 
 	if(!(jb = jabber_buddy_find(js, from, TRUE)))
 		return;
@@ -335,8 +470,10 @@
 
 
 	for(y = packet->child; y; y = y->next) {
+		const char *xmlns;
 		if(y->type != XMLNODE_TYPE_TAG)
 			continue;
+		xmlns = xmlnode_get_namespace(y);
 
 		if(!strcmp(y->name, "status")) {
 			g_free(status);
@@ -347,6 +484,11 @@
 				priority = atoi(p);
 				g_free(p);
 			}
+		} else if(!strcmp(y->name, "delay") && !strcmp(xmlns, "urn:xmpp:delay")) {
+			/* XXX: compare the time.  jabber:x:delay can happen on presence packets that aren't really and truly delayed */
+			delayed = TRUE;
+		} else if(!strcmp(y->name, "c") && !strcmp(xmlns, "http://jabber.org/protocol/caps")) {
+			caps = y; /* store for later, when creating buddy resource */
 		} else if(!strcmp(y->name, "x")) {
 			const char *xmlns = xmlnode_get_namespace(y);
 			if(xmlns && !strcmp(xmlns, "jabber:x:delay")) {
@@ -524,18 +666,22 @@
 		g_free(room_jid);
 	} else {
 		buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "",
-				jid->node ? "@" : "", jid->domain);
+									 jid->node ? "@" : "", jid->domain);
 		if((b = purple_find_buddy(js->gc->account, buddy_name)) == NULL) {
-			purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n",
-				buddy_name, purple_account_get_username(js->gc->account), js->gc->account);
-			jabber_id_free(jid);
-			g_free(avatar_hash);
-			g_free(buddy_name);
-			g_free(status);
-			return;
+			if(!jid->node || strcmp(jid->node,js->user->node) || strcmp(jid->domain,js->user->domain)) {
+				purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n",
+									 buddy_name, purple_account_get_username(js->gc->account), js->gc->account);
+				jabber_id_free(jid);
+				g_free(avatar_hash);
+				g_free(buddy_name);
+				g_free(status);
+				return;
+			} else {
+				/* this is a different resource of our own account. Resume even when this account isn't on our blist */
+			}
 		}
 
-		if(avatar_hash) {
+		if(b && avatar_hash) {
 			const char *avatar_hash2 = purple_buddy_icons_get_checksum_for_user(b);
 			if(!avatar_hash2 || strcmp(avatar_hash, avatar_hash2)) {
 				JabberIq *iq;
@@ -573,6 +719,19 @@
 		} else {
 			jbr = jabber_buddy_track_resource(jb, jid->resource, priority,
 					state, status);
+			if(caps) {
+				const char *node = xmlnode_get_attrib(caps,"node");
+				const char *ver = xmlnode_get_attrib(caps,"ver");
+				const char *ext = xmlnode_get_attrib(caps,"ext");
+				
+				if(node && ver) {
+					JabberPresenceCapabilities *userdata = g_new0(JabberPresenceCapabilities, 1);
+					userdata->js = js;
+					userdata->jbr = jbr;
+					userdata->from = g_strdup(from);
+					jabber_caps_get_info(js, from, node, ver, ext, jabber_presence_set_capabilities, userdata);
+				}
+			}
 		}
 
 		if((found_jbr = jabber_buddy_find_resource(jb, NULL))) {
--- a/libpurple/protocols/jabber/presence.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/presence.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_PRESENCE_H_
 #define _PURPLE_JABBER_PRESENCE_H_
@@ -27,7 +27,8 @@
 #include "xmlnode.h"
 
 void jabber_presence_send(PurpleAccount *account, PurpleStatus *status);
-xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority);
+xmlnode *jabber_presence_create(JabberBuddyState state, const char *msg, int priority); /* DEPRECATED */
+xmlnode *jabber_presence_create_js(JabberStream *js, JabberBuddyState state, const char *msg, int priority);
 void jabber_presence_parse(JabberStream *js, xmlnode *packet);
 void jabber_presence_subscription_set(JabberStream *js, const char *who,
 		const char *type);
--- a/libpurple/protocols/jabber/roster.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/roster.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -58,6 +58,7 @@
 {
 	GSList *buddies, *g2, *l;
 	gchar *my_bare_jid;
+	GList *pool = NULL;
 
 	buddies = purple_find_buddies(js->gc->account, jid);
 
@@ -89,13 +90,20 @@
 			g_free(l->data);
 			g2 = g_slist_delete_link(g2, l);
 		} else {
-			purple_blist_remove_buddy(b);
+			pool = g_list_prepend(pool, b);
 		}
 	}
 
 	while(g2) {
-		PurpleBuddy *b = purple_buddy_new(js->gc->account, jid, alias);
 		PurpleGroup *g = purple_find_group(g2->data);
+		PurpleBuddy *b = NULL;
+
+		if (pool) {
+			b = pool->data;
+			pool = g_list_delete_link(pool, pool);
+		} else {			
+			b = purple_buddy_new(js->gc->account, jid, alias);
+		}
 
 		if(!g) {
 			g = purple_group_new(g2->data);
@@ -121,6 +129,12 @@
 		g2 = g_slist_delete_link(g2, g2);
 	}
 
+	while (pool) {
+		PurpleBuddy *b = pool->data;
+		purple_blist_remove_buddy(b);
+		pool = g_list_delete_link(pool, pool);
+	}
+
 	g_free(my_bare_jid);
 	g_slist_free(buddies);
 }
--- a/libpurple/protocols/jabber/roster.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/roster.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_ROSTER_H_
 #define _PURPLE_JABBER_ROSTER_H_
--- a/libpurple/protocols/jabber/si.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/si.c	Wed Sep 12 19:11:38 2007 +0000
@@ -16,7 +16,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 
@@ -708,6 +708,10 @@
 
 	jabber_iq_set_callback(iq, jabber_si_xfer_send_method_cb, xfer);
 
+	/* Store the IQ id so that we can cancel the callback */
+	g_free(jsx->iq_id);
+	jsx->iq_id = g_strdup(iq->id);
+
 	jabber_iq_send(iq);
 }
 
@@ -722,6 +726,8 @@
 		purple_proxy_connect_cancel(jsx->connect_data);
 	if (jsx->listen_data != NULL)
 		purple_network_listen_cancel(jsx->listen_data);
+	if (jsx->iq_id != NULL)
+		jabber_iq_remove_callback_by_id(js, jsx->iq_id);
 
 	g_free(jsx->stream_id);
 	g_free(jsx->iq_id);
--- a/libpurple/protocols/jabber/si.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/si.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_SI_H_
 #define _PURPLE_JABBER_SI_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usermood.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,215 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "usermood.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+
+static const char *moodstrings[] = {
+	"afraid",
+	"amazed",
+	"angry",
+	"annoyed",
+	"anxious",
+	"aroused",
+	"ashamed",
+	"bored",
+	"brave",
+	"calm",
+	"cold",
+	"confused",
+	"contented",
+	"cranky",
+	"curious",
+	"depressed",
+	"disappointed",
+	"disgusted",
+	"distracted",
+	"embarrassed",
+	"excited",
+	"flirtatious",
+	"frustrated",
+	"grumpy",
+	"guilty",
+	"happy",
+	"hot",
+	"humbled",
+	"humiliated",
+	"hungry",
+	"hurt",
+	"impressed",
+	"in_awe",
+	"in_love",
+	"indignant",
+	"interested",
+	"intoxicated",
+	"invincible",
+	"jealous",
+	"lonely",
+	"mean",
+	"moody",
+	"nervous",
+	"neutral",
+	"offended",
+	"playful",
+	"proud",
+	"relieved",
+	"remorseful",
+	"restless",
+	"sad",
+	"sarcastic",
+	"serious",
+	"shocked",
+	"shy",
+	"sick",
+	"sleepy",
+	"stressed",
+	"surprised",
+	"thirsty",
+	"worried",
+	NULL
+};
+
+static void jabber_mood_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	const char *newmood = NULL;
+	char *moodtext = NULL;
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *moodinfo, *mood;
+	/* ignore the mood of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	mood = xmlnode_get_child_with_namespace(item, "mood", "http://jabber.org/protocol/mood");
+	if (!mood)
+		return;
+	for (moodinfo = mood->child; moodinfo; moodinfo = moodinfo->next) {
+		if (moodinfo->type == XMLNODE_TYPE_TAG) {
+			if (!strcmp(moodinfo->name, "text")) {
+				if (!moodtext) /* only pick the first one */
+					moodtext = xmlnode_get_data(moodinfo);
+			} else {
+				int i;
+				for (i = 0; moodstrings[i]; ++i) {
+					/* verify that the mood is known (valid) */
+					if (!strcmp(moodinfo->name, moodstrings[i])) {
+						newmood = moodstrings[i];
+						break;
+					}
+				}
+			}
+			if (newmood != NULL && moodtext != NULL)
+			   break;
+		}
+	}
+	if (newmood != NULL) {
+		const char *status_id;
+		JabberBuddyResource *resource = jabber_buddy_find_resource(buddy, NULL);
+		if(!resource) { /* huh? */
+			g_free(moodtext);
+			return;
+		}
+		status_id = jabber_buddy_state_get_status_id(resource->state);
+
+		purple_prpl_got_user_status(js->gc->account, from, status_id, "mood", _(newmood), "moodtext", moodtext?moodtext:"", NULL);
+	}
+	g_free(moodtext);
+}
+
+void jabber_mood_init(void) {
+	jabber_add_feature("mood", "http://jabber.org/protocol/mood", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("moodn", "http://jabber.org/protocol/mood", jabber_mood_cb);
+}
+
+static void do_mood_set_from_fields(PurpleConnection *gc, PurpleRequestFields *fields) {
+	JabberStream *js = gc->proto_data;
+	
+	jabber_mood_set(js, moodstrings[purple_request_fields_get_choice(fields, "mood")], purple_request_fields_get_string(fields, "text"));
+}
+
+static void do_mood_set_mood(PurplePluginAction *action) {
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+	int i;
+
+	fields = purple_request_fields_new();
+	group = purple_request_field_group_new(NULL);
+	purple_request_fields_add_group(fields, group);
+
+	field = purple_request_field_choice_new("mood",
+											_("Mood"), 0);
+	
+	for(i = 0; moodstrings[i]; ++i)
+		purple_request_field_choice_add(field, _(moodstrings[i]));
+	
+	purple_request_field_set_required(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	field = purple_request_field_string_new("text",
+											_("Description"), NULL,
+											FALSE);
+	purple_request_field_group_add_field(group, field);
+	
+	purple_request_fields(gc, _("Edit User Mood"),
+						  _("Edit User Mood"),
+						  _("Please select your mood from the list."),
+						  fields,
+						  _("Set"), G_CALLBACK(do_mood_set_from_fields),
+						  _("Cancel"), NULL,
+						  purple_connection_get_account(gc), NULL, NULL,
+						  gc);
+	
+}
+
+void jabber_mood_init_action(GList **m) {
+	PurplePluginAction *act = purple_plugin_action_new(_("Set Mood..."), do_mood_set_mood);
+	*m = g_list_append(*m, act);
+}
+
+void jabber_mood_set(JabberStream *js, const char *mood, const char *text) {
+	xmlnode *publish, *moodnode;
+	
+	assert(mood != NULL);
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/mood");
+	moodnode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "mood");
+	xmlnode_set_namespace(moodnode, "http://jabber.org/protocol/mood");
+	xmlnode_new_child(moodnode, mood);
+
+	if (text && text[0] != '\0') {
+		xmlnode *textnode = xmlnode_new_child(moodnode, "text");
+		xmlnode_insert_data(textnode, text, -1);
+	}
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+	   (yay for well-defined memory management rules) */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usermood.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,37 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERMOOD_H_
+#define _PURPLE_JABBER_USERMOOD_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0107 */
+
+void jabber_mood_init(void);
+
+void jabber_mood_init_action(GList **m);
+
+void jabber_mood_set(JabberStream *js,
+		     const char *mood, /* must be one of the valid strings defined in the XEP */
+		     const char *text /* might be NULL */);
+
+#endif /* _PURPLE_JABBER_USERMOOD_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usernick.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,102 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "usernick.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+#include "status.h"
+
+static void jabber_nick_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *nick;
+	const char *nickname = NULL;
+	
+	/* ignore the tune of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	nick = xmlnode_get_child_with_namespace(item, "nick", "http://jabber.org/protocol/nick");
+	if (!nick)
+		return;
+	nickname = xmlnode_get_data(nick);
+
+	serv_got_alias(js->gc, from, nickname);
+}
+
+static void do_nick_set(JabberStream *js, const char *nick) {
+	xmlnode *publish, *nicknode;
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/nick");
+	nicknode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "nick");
+	xmlnode_set_namespace(nicknode, "http://jabber.org/protocol/nick");
+	
+	if(nick && nick[0] != '\0')
+		xmlnode_insert_data(nicknode, nick, -1);
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+		(yay for well-defined memory management rules) */
+}
+
+static void do_nick_got_own_nick_cb(JabberStream *js, const char *from, xmlnode *items) {
+	const char *oldnickname = NULL;
+	xmlnode *item = xmlnode_get_child(items,"item");
+	
+	if(item) {
+		xmlnode *nick = xmlnode_get_child_with_namespace(item,"nick","http://jabber.org/protocol/nick");
+		if(nick)
+			oldnickname = xmlnode_get_data(nick);
+	}
+	
+	purple_request_input(js->gc, _("Set User Nickname"), _("Please specify a new nickname for you."),
+		_("This information is visible to all contacts on your contact list, so choose something appropriate."),
+		oldnickname, FALSE, FALSE, NULL, _("Set"), PURPLE_CALLBACK(do_nick_set), _("Cancel"), NULL,
+		purple_connection_get_account(js->gc), NULL, NULL, js);
+}
+
+static void do_nick_set_nick(PurplePluginAction *action) {
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	JabberStream *js = gc->proto_data;
+	char *jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain);
+	
+	/* since the nickname might have been changed by another resource of this account, we always have to request the old one
+		from the server to present as the default for the new one */
+	jabber_pep_request_item(js, jid, "http://jabber.org/protocol/nick", NULL, do_nick_got_own_nick_cb);
+	g_free(jid);
+}
+
+void jabber_nick_init(void) {
+	jabber_add_feature("nick", "http://jabber.org/protocol/nick", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("nickn", "http://jabber.org/protocol/nick", jabber_nick_cb);
+}
+
+void jabber_nick_init_action(GList **m) {
+	PurplePluginAction *act = purple_plugin_action_new(_("Set Nickname..."), do_nick_set_nick);
+	*m = g_list_append(*m, act);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usernick.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,32 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERNICK_H_
+#define _PURPLE_JABBER_USERNICK_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0172 */
+
+void jabber_nick_init(void);
+void jabber_nick_init_action(GList **m);
+
+#endif /* _PURPLE_JABBER_USERNICK_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usertune.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,124 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#include "internal.h"
+
+#include "usertune.h"
+#include "pep.h"
+#include <assert.h>
+#include <string.h>
+#include "internal.h"
+#include "request.h"
+#include "status.h"
+
+static void jabber_tune_cb(JabberStream *js, const char *from, xmlnode *items) {
+	/* it doesn't make sense to have more than one item here, so let's just pick the first one */
+	xmlnode *item = xmlnode_get_child(items, "item");
+	JabberBuddy *buddy = jabber_buddy_find(js, from, FALSE);
+	xmlnode *tuneinfo, *tune;
+	PurpleJabberTuneInfo tuneinfodata;
+	JabberBuddyResource *resource;
+	const char *status_id;
+	
+	/* ignore the tune of people not on our buddy list */
+	if (!buddy || !item)
+		return;
+	
+	tuneinfodata.artist = "";
+	tuneinfodata.title = "";
+	tuneinfodata.album = "";
+	tuneinfodata.track = "";
+	tuneinfodata.time = -1;
+	tuneinfodata.url = "";
+	
+	tune = xmlnode_get_child_with_namespace(item, "tune", "http://jabber.org/protocol/tune");
+	if (!tune)
+		return;
+	for (tuneinfo = tune->child; tuneinfo; tuneinfo = tuneinfo->next) {
+		if (tuneinfo->type == XMLNODE_TYPE_TAG) {
+			if (!strcmp(tuneinfo->name, "artist")) {
+				if (tuneinfodata.artist[0] == '\0') /* only pick the first one */
+					tuneinfodata.artist = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "length")) {
+				if (tuneinfodata.time == -1) {
+					char *length = xmlnode_get_data(tuneinfo);
+					if (length)
+						tuneinfodata.time = strtol(length, NULL, 10);
+				}
+			} else if (!strcmp(tuneinfo->name, "source")) {
+				if (tuneinfodata.album[0] == '\0') /* only pick the first one */
+					tuneinfodata.album = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "title")) {
+				if (tuneinfodata.title[0] == '\0') /* only pick the first one */
+					tuneinfodata.title = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "track")) {
+				if (tuneinfodata.track[0] == '\0') /* only pick the first one */
+					tuneinfodata.track = xmlnode_get_data(tuneinfo);
+			} else if (!strcmp(tuneinfo->name, "uri")) {
+				if (tuneinfodata.url[0] == '\0') /* only pick the first one */
+					tuneinfodata.url = xmlnode_get_data(tuneinfo);
+			}
+		}
+	}
+	resource = jabber_buddy_find_resource(buddy, NULL);
+	if(!resource)
+		return; /* huh? */
+	status_id = jabber_buddy_state_get_status_id(resource->state);
+
+	purple_prpl_got_user_status(js->gc->account, from, status_id, PURPLE_TUNE_ARTIST, tuneinfodata.artist, PURPLE_TUNE_TITLE, tuneinfodata.title, PURPLE_TUNE_ALBUM, tuneinfodata.album, PURPLE_TUNE_TRACK, tuneinfodata.track, PURPLE_TUNE_TIME, tuneinfodata.time, PURPLE_TUNE_URL, tuneinfodata.url, NULL);
+}
+
+void jabber_tune_init(void) {
+	jabber_add_feature("tune", "http://jabber.org/protocol/tune", jabber_pep_namespace_only_when_pep_enabled_cb);
+	jabber_pep_register_handler("tunen", "http://jabber.org/protocol/tune", jabber_tune_cb);
+}
+
+void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo) {
+	xmlnode *publish, *tunenode;
+	JabberStream *js = gc->proto_data;
+	
+	publish = xmlnode_new("publish");
+	xmlnode_set_attrib(publish,"node","http://jabber.org/protocol/tune");
+	tunenode = xmlnode_new_child(xmlnode_new_child(publish, "item"), "tune");
+	xmlnode_set_namespace(tunenode, "http://jabber.org/protocol/tune");
+	
+	if(tuneinfo) {
+		if(tuneinfo->artist && tuneinfo->artist[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "artist"),tuneinfo->artist,-1);
+		if(tuneinfo->title && tuneinfo->title[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "title"),tuneinfo->title,-1);
+		if(tuneinfo->album && tuneinfo->album[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "source"),tuneinfo->album,-1);
+		if(tuneinfo->url && tuneinfo->url[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "uri"),tuneinfo->url,-1);
+		if(tuneinfo->time >= 0) {
+			char *length = g_strdup_printf("%d", tuneinfo->time);
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "length"),length,-1);
+			g_free(length);
+		}
+		if(tuneinfo->track && tuneinfo->track[0] != '\0')
+			xmlnode_insert_data(xmlnode_new_child(tunenode, "track"),tuneinfo->track,-1);
+	}
+	
+	jabber_pep_publish(js, publish);
+	/* publish is freed by jabber_pep_publish -> jabber_iq_send -> jabber_iq_free
+	   (yay for well-defined memory management rules) */
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/jabber/usertune.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,43 @@
+/*
+ * purple - Jabber Protocol Plugin
+ *
+ * Copyright (C) 2007, Andreas Monitzer <andy@monitzer.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA	 02111-1307	 USA
+ *
+ */
+
+#ifndef _PURPLE_JABBER_USERTUNE_H_
+#define _PURPLE_JABBER_USERTUNE_H_
+
+#include "jabber.h"
+
+/* Implementation of XEP-0118 */
+
+typedef struct _PurpleJabberTuneInfo PurpleJabberTuneInfo;
+struct _PurpleJabberTuneInfo {
+	char *artist;
+	char *title;
+	char *album;
+	char *track; /* either the index of the track in the album or the URL for a stream */
+	int time; /* in seconds, -1 for unknown */
+	char *url;
+};
+
+void jabber_tune_init(void);
+
+void jabber_tune_set(PurpleConnection *gc, const PurpleJabberTuneInfo *tuneinfo);
+
+#endif /* _PURPLE_JABBER_USERTUNE_H_ */
--- a/libpurple/protocols/jabber/win32/posix.uname.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/win32/posix.uname.c	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
 
    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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
  */
 
 /*
--- a/libpurple/protocols/jabber/xdata.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/xdata.c	Wed Sep 12 19:11:38 2007 +0000
@@ -15,7 +15,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  *
  */
 #include "internal.h"
@@ -37,22 +37,39 @@
 struct jabber_x_data_data {
 	GHashTable *fields;
 	GSList *values;
-	jabber_x_data_cb cb;
+	jabber_x_data_action_cb cb;
 	gpointer user_data;
 	JabberStream *js;
+	GList *actions;
+	PurpleRequestFieldGroup *actiongroup;
 };
 
 static void jabber_x_data_ok_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
 	xmlnode *result = xmlnode_new("x");
-	jabber_x_data_cb cb = data->cb;
+	jabber_x_data_action_cb cb = data->cb;
 	gpointer user_data = data->user_data;
 	JabberStream *js = data->js;
 	GList *groups, *flds;
+	char *actionhandle = NULL;
+	gboolean hasActions = (data->actions != NULL);
 
 	xmlnode_set_namespace(result, "jabber:x:data");
 	xmlnode_set_attrib(result, "type", "submit");
 
 	for(groups = purple_request_fields_get_groups(fields); groups; groups = groups->next) {
+		if(groups->data == data->actiongroup) {
+			for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
+				PurpleRequestField *field = flds->data;
+				const char *id = purple_request_field_get_id(field);
+				int handleindex;
+				if(strcmp(id, "libpurple:jabber:xdata:actions"))
+					continue;
+				handleindex = purple_request_field_choice_get_value(field);
+				actionhandle = g_strdup(g_list_nth_data(data->actions, handleindex));
+				break;
+			}
+			continue;
+		}
 		for(flds = purple_request_field_group_get_fields(groups->data); flds; flds = flds->next) {
 			xmlnode *fieldnode, *valuenode;
 			PurpleRequestField *field = flds->data;
@@ -127,31 +144,59 @@
 		g_free(data->values->data);
 		data->values = g_slist_delete_link(data->values, data->values);
 	}
+	if (data->actions) {
+		GList *action;
+		for(action = data->actions; action; action = g_list_next(action)) {
+			g_free(action->data);
+		}
+		g_list_free(data->actions);
+	}
 	g_free(data);
 
-	cb(js, result, user_data);
+	if (hasActions) {
+		cb(js, result, actionhandle, user_data);
+		g_free(actionhandle);
+	} else
+		((jabber_x_data_cb)cb)(js, result, user_data);
 }
 
 static void jabber_x_data_cancel_cb(struct jabber_x_data_data *data, PurpleRequestFields *fields) {
 	xmlnode *result = xmlnode_new("x");
-	jabber_x_data_cb cb = data->cb;
+	jabber_x_data_action_cb cb = data->cb;
 	gpointer user_data = data->user_data;
 	JabberStream *js = data->js;
+	gboolean hasActions = FALSE;
 	g_hash_table_destroy(data->fields);
 	while(data->values) {
 		g_free(data->values->data);
 		data->values = g_slist_delete_link(data->values, data->values);
 	}
+	if (data->actions) {
+		GList *action;
+		hasActions = TRUE;
+		for(action = data->actions; action; action = g_list_next(action)) {
+			g_free(action->data);
+		}
+		g_list_free(data->actions);
+	}
 	g_free(data);
 
 	xmlnode_set_namespace(result, "jabber:x:data");
 	xmlnode_set_attrib(result, "type", "cancel");
 
-	cb(js, result, user_data);
+	if (hasActions)
+		cb(js, result, NULL, user_data);
+	else
+		((jabber_x_data_cb)cb)(js, result, user_data);
 }
 
 void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data)
 {
+	return jabber_x_data_request_with_actions(js, packet, NULL, 0, (jabber_x_data_action_cb)cb, user_data);
+}
+
+void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data)
+{
 	void *handle;
 	xmlnode *fn, *x;
 	PurpleRequestFields *fields;
@@ -180,7 +225,7 @@
 		char *value = NULL;
 
 		if(!type)
-			continue;
+			type = "text-single";
 
 		if(!var && strcmp(type, "fixed"))
 			continue;
@@ -191,8 +236,6 @@
 			value = xmlnode_get_data(valuenode);
 
 
-		/* XXX: handle <required/> */
-
 		if(!strcmp(type, "text-private")) {
 			if((valuenode = xmlnode_get_child(fn, "value")))
 				value = xmlnode_get_data(valuenode);
@@ -324,6 +367,26 @@
 
 			g_free(value);
 		}
+
+		if(field && xmlnode_get_child(fn, "required"))
+			purple_request_field_set_required(field,TRUE);
+	}
+	
+	if(actions != NULL) {
+		PurpleRequestField *actionfield;
+		GList *action;
+		data->actiongroup = group = purple_request_field_group_new(_("Actions"));
+		purple_request_fields_add_group(fields, group);
+		actionfield = purple_request_field_choice_new("libpurple:jabber:xdata:actions", _("Select an action"), defaultaction);
+
+		for(action = actions; action; action = g_list_next(action)) {
+			JabberXDataAction *a = action->data;
+			
+			purple_request_field_choice_add(actionfield, a->name);
+			data->actions = g_list_append(data->actions, g_strdup(a->handle));
+		}
+		purple_request_field_set_required(actionfield,TRUE);
+		purple_request_field_group_add_field(group, actionfield);
 	}
 
 	if((x = xmlnode_get_child(packet, "title")))
--- a/libpurple/protocols/jabber/xdata.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/jabber/xdata.h	Wed Sep 12 19:11:38 2007 +0000
@@ -17,7 +17,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _PURPLE_JABBER_XDATA_H_
 #define _PURPLE_JABBER_XDATA_H_
@@ -25,7 +25,14 @@
 #include "jabber.h"
 #include "xmlnode.h"
 
+typedef struct _JabberXDataAction {
+	char *name;
+	char *handle;
+} JabberXDataAction;
+
 typedef void (*jabber_x_data_cb)(JabberStream *js, xmlnode *result, gpointer user_data);
+typedef void (*jabber_x_data_action_cb)(JabberStream *js, xmlnode *result, const char *actionhandle, gpointer user_data);
 void *jabber_x_data_request(JabberStream *js, xmlnode *packet, jabber_x_data_cb cb, gpointer user_data);
+void *jabber_x_data_request_with_actions(JabberStream *js, xmlnode *packet, GList *actions, int defaultaction, jabber_x_data_action_cb cb, gpointer user_data);
 
 #endif /* _PURPLE_JABBER_XDATA_H_ */
--- a/libpurple/protocols/msn/cmdproc.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "cmdproc.h"
--- a/libpurple/protocols/msn/cmdproc.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/cmdproc.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_CMDPROC_H_
 #define _MSN_CMDPROC_H_
--- a/libpurple/protocols/msn/command.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/command.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "command.h"
--- a/libpurple/protocols/msn/command.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/command.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_COMMAND_H
 #define _MSN_COMMAND_H
--- a/libpurple/protocols/msn/dialog.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/dialog.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
 #include "msn.h"
--- a/libpurple/protocols/msn/dialog.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/dialog.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_DIALOG_H_
 #define _MSN_DIALOG_H_
--- a/libpurple/protocols/msn/directconn.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/directconn.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "directconn.h"
--- a/libpurple/protocols/msn/directconn.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/directconn.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_DIRECTCONN_H_
 #define _MSN_DIRECTCONN_H_
--- a/libpurple/protocols/msn/error.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/error.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "error.h"
--- a/libpurple/protocols/msn/error.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/error.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_ERROR_H_
 #define _MSN_ERROR_H_
--- a/libpurple/protocols/msn/group.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/group.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "group.h"
--- a/libpurple/protocols/msn/group.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/group.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_GROUP_H_
 #define _MSN_GROUP_H_
--- a/libpurple/protocols/msn/history.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/history.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "history.h"
--- a/libpurple/protocols/msn/history.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/history.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_HISTORY_H
 #define _MSN_HISTORY_H
--- a/libpurple/protocols/msn/httpconn.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/httpconn.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "debug.h"
@@ -181,7 +181,7 @@
 		{
 			msn_session_set_error(httpconn->session,
 								  MSN_ERROR_HTTP_MALFORMED, NULL);
-			purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}",
+			purple_debug_error("msn", "Malformed X-MSN-Messenger field.\n{%s}\n",
 							 buf);
 
 			g_free(body);
--- a/libpurple/protocols/msn/httpconn.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/httpconn.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_HTTPCONN_H_
 #define _MSN_HTTPCONN_H_
--- a/libpurple/protocols/msn/msg.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msg.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "msg.h"
@@ -400,7 +400,7 @@
 	GList *l;
 	char *n, *base, *end;
 	int len;
-	size_t body_len;
+	size_t body_len = 0;
 	const void *body;
 
 	g_return_val_if_fail(msg != NULL, NULL);
--- a/libpurple/protocols/msn/msg.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msg.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_MSG_H_
 #define _MSN_MSG_H_
--- a/libpurple/protocols/msn/msn-utils.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msn-utils.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "msn-utils.h"
--- a/libpurple/protocols/msn/msn-utils.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msn-utils.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_UTILS_H_
 #define _MSN_UTILS_H_
--- a/libpurple/protocols/msn/msn.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #define PHOTO_SUPPORT 1
 
@@ -100,25 +100,53 @@
 	return buf;
 }
 
-static PurpleCmdRet
-msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+static gboolean
+msn_send_attention(PurpleConnection *gc, const char *username, guint type)
 {
-	PurpleAccount *account = purple_conversation_get_account(conv);
-	PurpleConnection *gc = purple_account_get_connection(account);
 	MsnMessage *msg;
 	MsnSession *session;
 	MsnSwitchBoard *swboard;
 
 	msg = msn_message_new_nudge();
 	session = gc->proto_data;
-	swboard = msn_session_get_swboard(session, purple_conversation_get_name(conv), MSN_SB_FLAG_IM);
+	swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM);
 
 	if (swboard == NULL)
-		return PURPLE_CMD_RET_FAILED;
+		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
 
-	purple_conversation_write(conv, NULL, _("You have just sent a Nudge!"), PURPLE_MESSAGE_SYSTEM, time(NULL));
+	return TRUE;
+}
+
+static GList *
+msn_attention_types(PurpleAccount *account)
+{
+	PurpleAttentionType *attn;
+	static GList *list = NULL;
+
+	if (!list) {
+		attn = g_new0(PurpleAttentionType, 1);
+		attn->name = _("Nudge");
+		attn->incoming_description = _("%s has nudged you!");
+		attn->outgoing_description = _("Nudging %s...");
+		list = g_list_append(list, attn);
+	}
+
+	return list;
+}
+
+
+static PurpleCmdRet
+msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
+{
+	PurpleAccount *account = purple_conversation_get_account(conv);
+	PurpleConnection *gc = purple_account_get_connection(account);
+	const gchar *username;
+
+	username = purple_conversation_get_name(conv);
+
+	serv_send_attention(gc, username, MSN_NUDGE);
 
 	return PURPLE_CMD_RET_OK;
 }
@@ -2101,11 +2129,11 @@
 	NULL,					/* whiteboard_prpl_ops */
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
+	NULL,					/* unregister_user */
+	msn_send_attention,                     /* send_attention */
+	msn_attention_types,                    /* attention_types */
 
 	/* padding */
-	NULL,
-	NULL,
-	NULL,
 	NULL
 };
 
--- a/libpurple/protocols/msn/msn.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/msn.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_H_
 #define _MSN_H_
@@ -79,6 +79,8 @@
 	"Client-Name: Purple/" VERSION "\r\n" \
 	"Chat-Logging: Y\r\n"
 
+/* Index into attention_types */
+#define MSN_NUDGE 0
 
 typedef enum
 {
--- a/libpurple/protocols/msn/nexus.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/nexus.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "nexus.h"
@@ -188,7 +188,7 @@
 	purple_ssl_close(nexus->gsc);
 	nexus->gsc = NULL;
 
-	purple_debug_misc("msn", "ssl buffer: {%s}", nexus->read_buf);
+	purple_debug_misc("msn", "ssl buffer: {%s}\n", nexus->read_buf);
 
 	if (strstr(nexus->read_buf, "HTTP/1.1 302") != NULL)
 	{
--- a/libpurple/protocols/msn/nexus.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/nexus.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_NEXUS_H_
 #define _MSN_NEXUS_H_
--- a/libpurple/protocols/msn/notification.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/notification.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "notification.h"
--- a/libpurple/protocols/msn/notification.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/notification.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_NOTIFICATION_H_
 #define _MSN_NOTIFICATION_H_
--- a/libpurple/protocols/msn/object.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/object.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "object.h"
 #include "debug.h"
--- a/libpurple/protocols/msn/object.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/object.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_OBJECT_H_
 #define _MSN_OBJECT_H_
--- a/libpurple/protocols/msn/page.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/page.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "page.h"
--- a/libpurple/protocols/msn/page.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/page.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_PAGE_H_
 #define _MSN_PAGE_H_
--- a/libpurple/protocols/msn/servconn.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/servconn.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "servconn.h"
@@ -51,7 +51,7 @@
 	servconn->num = session->servconns_count++;
 
 	servconn->tx_buf = purple_circ_buffer_new(MSN_BUF_LEN);
-	servconn->tx_handler = -1;
+	servconn->tx_handler = 0;
 
 	return servconn;
 }
@@ -303,7 +303,7 @@
 
 	if (writelen == 0) {
 		purple_input_remove(servconn->tx_handler);
-		servconn->tx_handler = -1;
+		servconn->tx_handler = 0;
 		return;
 	}
 
@@ -328,7 +328,7 @@
 
 	if (!servconn->session->http_method)
 	{
-		if (servconn->tx_handler == -1) {
+		if (servconn->tx_handler == 0) {
 			switch (servconn->type)
 			{
 				case MSN_SERVCONN_NS:
@@ -353,7 +353,7 @@
 		if (ret < 0 && errno == EAGAIN)
 			ret = 0;
 		if (ret >= 0 && ret < len) {
-			if (servconn->tx_handler == -1)
+			if (servconn->tx_handler == 0)
 				servconn->tx_handler = purple_input_add(
 					servconn->fd, PURPLE_INPUT_WRITE,
 					servconn_write_cb, servconn);
--- a/libpurple/protocols/msn/servconn.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/servconn.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SERVCONN_H_
 #define _MSN_SERVCONN_H_
--- a/libpurple/protocols/msn/session.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/session.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "session.h"
--- a/libpurple/protocols/msn/session.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/session.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SESSION_H_
 #define _MSN_SESSION_H_
--- a/libpurple/protocols/msn/slp.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slp.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "slp.h"
@@ -343,7 +343,7 @@
 		if (xfer)
 		{
 			bin = (char *)purple_base64_decode(context, &bin_len);
-			file_size = GUINT32_FROM_LE(*((gsize *)bin + 2));
+			file_size = GUINT32_FROM_LE(*(gsize *)(bin + 2));
 
 			uni_name = (gunichar2 *)(bin + 20);
 			while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) {
--- a/libpurple/protocols/msn/slp.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slp.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SLP_H_
 #define _MSN_SLP_H_
--- a/libpurple/protocols/msn/slpcall.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "slpcall.h"
--- a/libpurple/protocols/msn/slpcall.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpcall.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SLPCALL_H_
 #define _MSN_SLPCALL_H_
--- a/libpurple/protocols/msn/slplink.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slplink.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "slplink.h"
@@ -49,7 +49,7 @@
 	tf = g_fopen(tmp, "wb");
 	if (tf == NULL)
 	{
-		purple_debug_error("msn", "could not open debug file");
+		purple_debug_error("msn", "could not open debug file\n");
 		return;
 	}
 	pload = msn_message_gen_payload(msg, &pload_size);
--- a/libpurple/protocols/msn/slplink.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slplink.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SLPLINK_H_
 #define _MSN_SLPLINK_H_
--- a/libpurple/protocols/msn/slpmsg.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpmsg.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "slpmsg.h"
--- a/libpurple/protocols/msn/slpmsg.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpmsg.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SLPMSG_H_
 #define _MSN_SLPMSG_H_
--- a/libpurple/protocols/msn/slpsession.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpsession.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "slpsession.h"
 
--- a/libpurple/protocols/msn/slpsession.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/slpsession.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SLPSESSION_H_
 #define _MSN_SLPSESSION_H_
--- a/libpurple/protocols/msn/state.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/state.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "state.h"
--- a/libpurple/protocols/msn/state.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/state.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_STATE_H_
 #define _MSN_STATE_H_
--- a/libpurple/protocols/msn/switchboard.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "prefs.h"
@@ -771,7 +771,8 @@
 		msg->ack_cb(msg, msg->ack_data);
 
 	swboard = cmdproc->data;
-	swboard->ack_list = g_list_remove(swboard->ack_list, msg);
+	if (swboard)
+		swboard->ack_list = g_list_remove(swboard->ack_list, msg);
 	msn_message_unref(msg);
 }
 
@@ -951,6 +952,8 @@
 	PurpleBuddy *buddy;
 	const char *user;
 
+	str = NULL;
+
 	swboard = cmdproc->data;
 	account = cmdproc->session->account;
 	user = msg->remote_user;
@@ -960,9 +963,8 @@
 	else
 		username = g_markup_escape_text(user, -1);
 
-	str = g_strdup_printf(_("%s just sent you a Nudge!"), username);
+	serv_got_attention(account->gc, buddy->name, MSN_NUDGE);
 	g_free(username);
-	msn_switchboard_report_user(swboard, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, str);
 	g_free(str);
 }
 
--- a/libpurple/protocols/msn/switchboard.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/switchboard.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SWITCHBOARD_H_
 #define _MSN_SWITCHBOARD_H_
--- a/libpurple/protocols/msn/sync.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/sync.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "sync.h"
--- a/libpurple/protocols/msn/sync.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/sync.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_SYNC_H_
 #define _MSN_SYNC_H_
--- a/libpurple/protocols/msn/table.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/table.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "table.h"
--- a/libpurple/protocols/msn/table.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/table.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_TABLE_H_
 #define _MSN_TABLE_H_
--- a/libpurple/protocols/msn/transaction.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/transaction.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "transaction.h"
--- a/libpurple/protocols/msn/transaction.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/transaction.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_TRANSACTION_H
 #define _MSN_TRANSACTION_H
--- a/libpurple/protocols/msn/user.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/user.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "user.h"
--- a/libpurple/protocols/msn/user.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/user.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_USER_H_
 #define _MSN_USER_H_
--- a/libpurple/protocols/msn/userlist.c	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/userlist.c	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #include "msn.h"
 #include "userlist.h"
--- a/libpurple/protocols/msn/userlist.h	Sun Aug 19 13:40:34 2007 +0000
+++ b/libpurple/protocols/msn/userlist.h	Wed Sep 12 19:11:38 2007 +0000
@@ -19,7 +19,7 @@
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 #ifndef _MSN_USERLIST_H_
 #define _MSN_USERLIST_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/CHANGES	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,156 @@
+2007-08-28 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.17
+* Get server-side contact list from server on sign-on (partly implements
+  server-side contacts, ticket #2658).
+* Set local alias to username on sign-on, if not already set. This fixes
+  #2793, though this may not be the best way, other fixes under consideration.
+* Support myim:sendIM and addContact URLs with cID and uID parameters.
+* Fix #2722, only check for mail if "New mail notifications" is enabled.
+* Modularize msimprpl.
+* Pidgin 2.1.1 only.
+
+2007-08-23 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.16
+* Add option to add all friends from myspace.com to your buddy list (#2660)
+* If a user doesn't have a picture, don't display an icon (instead of
+  displaying MySpace's "no photo" icon)
+* Fix #2725, a common crash related to buddy icon data
+* Fix #2752, which led to duplicate groups
+* Fix #2720, crash/disconnect when adding a buddy that doesn't exist
+  (You'll now receive an error when looking up invalid usernames).
+* Source-code release only.
+
+2007-08-22 Jeff Connelly <pidgin@xyzzy.cjb.net> - 0.15
+* Incomplete implementation of adding friends from myspace.com.
+* Change msim_msg_get() to start at the given node instead of the beginning.
+* Add msim_msg_get_*_from_element() to access data in MsimMessagElement *'s.
+* Use MsimMessage dictionaries everywhere in incoming messages, instead of
+  the old GHashTable method. Dictionary type is now fully implemented.
+* Add functions to loop over MsimMessages.
+* Link to myspace.com profile in Get Info.
+* Conditionally use my proposed attention API if defined.
+* Propagate to im.pidgin.pidgin branch for 2.1.2.
+* GSoC ended on 2007-08-20. The code in this release hasn't changed since
+  then. I did, however, bump the version number to 0.15 to distinguish this
+  release from the previous one. But there were no code changes. I updated
+  the text files, too.
+* Note: msimprpl will continue to be developed as time permits.
+
+2007-08-12 Jeff Connelly <jeff2@soc.pidgin.im> - 0.14
+* Full emoticon support (except no difference between nerd and geek emoticons),
+  thanks to a number of new icons from Hylke Bons.
+* Package Win32 release archive so that it can easily be extracted directly
+  into the folder Pidgin was installed to.
+* Better password handling, may now support Unicode passwords.
+* Much general clean-up and restructuring of the code.
+* Resolve user ID from buddy list, if it exists. Greatly improves speed of 
+  receiving messages from user IDs.
+* Support sending and receiving hyperlinks.
+* Fix #2521 by reimplementing protocol message escaping to work correctly.
+* Fix #2520 by indicating sign-on at the correct time.
+
+2007-08-04 Jeff Connelly <jeff2@soc.pidgin.im> - 0.13
+* Fix crash when deleting buddies, on Windows.
+* Disable sending client version to oncoming buddies (compile-time option).
+* Updated login process (more closely resembles official client).
+* Zaps, sending and receiving
+* Emoticons, mapped to Pidgin-supported smileys
+* Show official client build in buddy profiles.
+
+2007-07-15 Jeff Connelly <jeff2@soc.pidgin.im> - 0.12
+* Allow logging in with passwords containing uppercase letters (bug #2066)
+* Add /3 -> | translation to escaping.
+* Allow setting status string.
+* Disable keepalive timeout.
+* Remove faking self online, instead show real status (now that it exists).
+* Support font sizes in incoming instant messages.
+* Add support for mail notifications.
+
+2007-07-09 Jeff Connelly <jeff2@soc.pidgin.im> - 0.11
+* Allow going idle (tested with I'dle Ma'ker) and viewing idle status of 
+  buddies (thanks to Scott Ellis, developing a MySpaceIM plugin for Miranda IM,
+  for finding the idle status code.)
+* Time out if no data from server within a certain amount of time 
+  (keep alives).
+* Remove "Sign on as hidden" option, and always set status to current status
+  when signing on.
+* Some support for sending formatted text.
+* Fix build process on Unix, bug #2086.
+
+2007-07-03 Jeff Connelly <jeff2@soc.pidgin.im> - 0.10
+* On incoming instant messages, add support for:
+ * Text color
+ * Font face
+* Add option to sign on as hidden, default off (previously, always was hidden)
+* Add ability to change status to hidden, available, away
+* Increase password length limit to 10 to match official client (bug #2010)
+
+2007-07-01 Jeff Connelly <jeff2@soc.pidgin.im> - 0.9
+* Fix crash on Windows when logging in (bug #1990)
+* Fix crash on Windows when viewing tooltip text (bug #1999)
+
+2007-06-30 Jeff Connelly <jeff2@soc.pidgin.im> - 0.8
+* Allow "Get Info" on all users, by uid or username
+* Fix crash when re-logging in, if login failed.
+* Show descriptive error message if login password is too long.
+* Fake self from being online, since can't add self to buddy list.
+* Update for Libpurple 2.0.2.
+* Partial support for formatting on incoming instant messages.
+
+2007-06-14 Jeff Connelly <jeff2@soc.pidgin.im> - 0.7
+* Add/delete buddy now functional (required many other code improvements).
+* Show improved buddy information in tooltip text.
+* Show user profile (in "Get Info" option) for buddies on buddy list.
+* Fix crash when re-logging in, if login succeeded.
+
+2007-06-12 Jeff Connelly <jeff2@soc.pidgin.im> - 0.6
+* Use RC4 code from Libpurple 2.0.1
+* Use a new implementation for sending and receiving messages (MsimMessage).
+  This infrastructural change significantly improves extensibility.
+* Show online buddies as online.
+* Send and receive typing notifications (along with other required changes).
+
+2007-05-22 Jeff Connelly <jeff2@soc.pidgin.im> - 0.5
+* Add protocol escaping, so can now send and receive / and \ characters
+* Designed Pidgin 2.0.0beta7
+* Use RC4 code from Samba
+* Use translations (_ macro)
+* No major changes to code, still getting familiar with tools & community
+
+2007-04-29 Jeff Connelly <jeff2@soc.pidgin.im> 
+
+* NOTE: This code is now being developed under Monotone, in the
+  im.pidgin.soc.2007.msimprpl branch on my local computer, which
+  is periodically sync'd with pidgin.im's Monotone database.
+
+  Changes will be logged to Monotone.
+
+2007-04-15 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.4
+
+* Gracefully handle a full receive buffer
+* Handle fatal errors
+* Last version for Gaim 2.0.0beta6
+
+2007-04-14 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.3
+
+* Win32 support
+* Add a large number of precondition checks and a handful of assertions
+* Add documentation to each function, for doxygen.
+
+2007-04-10 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.2
+
+* Add ability to IM by email address.
+* Show usernames on buddy list instead of userids.
+* Show incoming messages as coming from username, instead of userid.
+* Add status messages and tooltip text.
+
+2007-04-09 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.1
+
+* Parsing most of the protocol.
+* Logging in using RC4/SHA1-based authentication.
+* Sending messages, by numeric userid or username.
+* Receiving messages, currently only by numeric userid.
+* Some buddy list support (show all users on buddy list as online, by uid).
+
+2007-04-07 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.0
+
+* Initial version. Login only. Not publicly released.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/ChangeLog	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,39 @@
+
+2007-04-29 Jeff Connelly <jeff2@soc.pidgin.com> 
+
+* NOTE: This code is now being developed under Monotone, in the
+  im.pidgin.soc.2007.msimprpl branch on my local computer, which
+  is periodically sync'd with pidgin.im's Monotone database.
+
+  Changes will be logged to Monotone.
+
+2007-04-15 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.4
+
+* Gracefully handle a full receive buffer
+* Handle fatal errors
+
+2007-04-14 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.3
+
+* Win32 support
+* Add a large number of precondition checks and a handful of assertions
+* Add documentation to each function, for doxygen.
+
+2007-04-10 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.2
+
+* Add ability to IM by email address.
+* Show usernames on buddy list instead of userids.
+* Show incoming messages as coming from username, instead of userid.
+* Add status messages and tooltip text.
+
+2007-04-09 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.1
+
+* Parsing most of the protocol.
+* Logging in using RC4/SHA1-based authentication.
+* Sending messages, by numeric userid or username.
+* Receiving messages, currently only by numeric userid.
+* Some buddy list support (show all users on buddy list as online, by uid).
+
+2007-04-07 Jeff Connelly <myspaceim@xyzzy.cjb.net> - 0.0
+
+* Initial version. Login only. Not publicly released.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/LICENSE	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/Makefile.am	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,41 @@
+EXTRA_DIST = Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+SOURCES = myspace.c \
+	  myspace.h \
+	  persist.h \
+	  message.c \
+	  message.h \
+	  zap.c \
+	  session.c \
+	  session.h \
+	  markup.c \
+	  markup.h \
+	  user.c \
+	  user.h
+
+AM_CFLAGS = $(st)
+
+libmyspace_la_LDFLAGS = -module -avoid-version
+
+if STATIC_MYSPACE
+
+st = -DPURPLE_STATIC_PRPL
+noinst_LIBRARIES     = libmyspace.a
+libmyspace_a_SOURCES = $(SOURCES)
+libmyspace_a_CFLAGS  = $(AM_CFLAGS)
+
+else
+
+st =
+pkg_LTLIBRARIES       = libmyspace.la
+libmyspace_la_SOURCES = $(SOURCES)
+libmyspace_la_LIBADD  = $(GLIB_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libpurple \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/Makefile.mingw	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,81 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libmyspace
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libmyspace
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+  DEFINES += -DSTATIC
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS +=	-I. \
+			-I$(GTK_TOP)/include \
+			-I$(GTK_TOP)/include/glib-2.0 \
+			-I$(GTK_TOP)/lib/glib-2.0/include \
+			-I$(PURPLE_TOP) \
+			-I$(PURPLE_TOP)/win32 \
+			-I$(PIDGIN_TREE_TOP)
+
+LIB_PATHS =		-L$(GTK_TOP)/lib \
+			-L$(PURPLE_TOP)
+
+##
+##  SOURCES, OBJECTS
+##
+C_SRC =			myspace.c message.c zap.c session.c markup.c user.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS =	\
+			-lglib-2.0 \
+			-lws2_32 \
+			-lintl \
+			-lpurple
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR)
+	cp $(TARGET).dll $(DLL_INSTALL_DIR)
+
+$(OBJECTS): $(PURPLE_CONFIG_H)
+
+##
+## BUILD DLL
+##
+$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+
+clean:
+	rm -f $(OBJECTS)
+	rm -f $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/README	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,32 @@
+MySpaceIM Protocol Plugin for Libpurple     by Jeff Connelly 20070807
+
+
+Greetings. This package contains a plugin for libpurple (as used in
+Pidgin, formerly Gaim) to connect to the new MySpaceIM instant messaging 
+network and send/receive messages. Functionality is only basic as of yet, 
+and this code should be considered alpha quality.
+
+This code was initially developed under Google Summer of Code 2007.
+
+For features and TODO, see http://developer.pidgin.im/wiki/MySpaceIM
+
+Windows installation: Unzip the archive to C:\Program Files\Pidgin
+Unix/source installation: run "make install"
+
+Usage:
+
+Login using your _email address_ you use to login to myspace.com. You can't
+login using your numeric ID or alias.
+
+To test it out, send a message to yourself (by your username or numeric 
+uid (email not yet supported)) or tom (6221). In either case you should 
+get a reply. You should also be able to talk to other MySpaceIM users if 
+you desire. Replies will always be shown as coming from a user's username, 
+even if you IM by email or userid.
+
+Feedback welcome. You can IM my test account at "msimprpl" if you feel like it.
+
+Enjoy,
+-Jeff Connelly
+msimprpl@xyzzy.cjb.net
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,691 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * 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 "myspace.h"
+
+typedef void (*MSIM_XMLNODE_CONVERT)(MsimSession *, xmlnode *, gchar **, gchar **);
+
+/* Internal functions */
+
+static guint msim_point_to_purple_size(MsimSession *session, guint point);
+static guint msim_purple_size_to_point(MsimSession *session, guint size);
+static guint msim_height_to_point(MsimSession *session, guint height);
+static guint msim_point_to_height(MsimSession *session, guint point);
+
+static void msim_markup_tag_to_html(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static void html_tag_to_msim_markup(MsimSession *, xmlnode *root, gchar **begin, gchar **end);
+static gchar *msim_convert_xml(MsimSession *, const gchar *raw, MSIM_XMLNODE_CONVERT f);
+static gchar *msim_convert_smileys_to_markup(gchar *before);
+static double msim_round(double round);
+
+
+/* Globals */
+
+/* The names in in emoticon_names (for <i n=whatever>) map to corresponding 
+ * entries in emoticon_symbols (for the ASCII representation of the emoticon).
+ *
+ * Multiple emoticon symbols in Pidgin can map to one name. List the
+ * canonical form, as inserted by the "Smile!" dialog, first. For example,
+ * :) comes before :-), because although both are recognized as 'happy',
+ * the first is inserted by the smiley button (first symbol in theme).
+ *
+ * Note that symbols are case-sensitive in Pidgin -- :-X is not :-x. */
+static struct MSIM_EMOTICON
+{
+	gchar *name;
+	gchar *symbol;
+} msim_emoticons[] = {
+	/* Unfortunately, this list duplicates much of the file
+	 * pidgin/pidgin/pixmaps/emotes/default/22/default.theme.in, because
+	 * that file is part of Pidgin, but we're part of libpurple.
+	 */
+	{ "bigsmile", ":D" },
+	{ "bigsmile", ":-D" },
+	{ "devil", "}:)" },
+	{ "frazzled", ":Z" },
+	{ "geek", "B)" },
+	{ "googles", "%)" },
+	{ "growl", ":E" },
+	{ "laugh", ":))" },		/* Must be before ':)' */
+	{ "happy", ":)" },
+	{ "happy", ":-)" },
+	{ "happi", ":)" },
+	{ "heart", ":X" },
+	{ "mohawk", "-:" },
+	{ "mad", "X(" },
+	{ "messed", "X)" },
+	{ "nerd", "Q)" },
+	{ "oops", ":G" },
+	{ "pirate", "P)" },
+	{ "scared", ":O" },
+	{ "sidefrown", ":{" },
+	{ "sinister", ":B" },
+	{ "smirk", ":," },
+	{ "straight", ":|" },
+	{ "tongue", ":P" },
+	{ "tongue", ":p" },
+	{ "tongy", ":P" },
+	{ "upset", "B|" },
+	{ "wink", ";-)" },
+	{ "wink", ";)" },
+	{ "winc", ";)" },
+	{ "worried", ":[" },
+	{ "kiss", ":x" },
+	{ NULL, NULL }
+};
+
+
+
+/* Indexes of this array + 1 map HTML font size to scale of normal font size. *
+ * Based on _point_sizes from libpurple/gtkimhtml.c 
+ *                                 1    2  3    4     5      6       7 */
+static gdouble _font_scale[] = { .85, .95, 1, 1.2, 1.44, 1.728, 2.0736 };
+
+#define MAX_FONT_SIZE                   7       /* Purple maximum font size */
+#define POINTS_PER_INCH                 72      /* How many pt's in an inch */
+
+/* Text formatting bits for <f s=#> */
+#define MSIM_TEXT_BOLD                  1
+#define MSIM_TEXT_ITALIC                2   
+#define MSIM_TEXT_UNDERLINE             4
+
+/* Default baseline size of purple's fonts, in points. What is size 3 in points. 
+ * _font_scale specifies scaling factor relative to this point size. Note this 
+ * is only the default; it is configurable in account options. */
+#define MSIM_BASE_FONT_POINT_SIZE       8
+
+/* Default display's DPI. 96 is common but it can differ. Also configurable
+ * in account options. */
+#define MSIM_DEFAULT_DPI                96
+
+
+/* round is part of C99, but sometimes is unavailable before then.
+ * Based on http://forums.belution.com/en/cpp/000/050/13.shtml
+ */
+double msim_round(double value)
+{
+	if (value < 0) {
+		return -(floor(-value + 0.5));
+	} else {
+		return   floor( value + 0.5);
+	}
+}
+
+
+/** Convert typographical font point size to HTML font size. 
+ * Based on libpurple/gtkimhtml.c */
+static guint
+msim_point_to_purple_size(MsimSession *session, guint point)
+{
+	guint size, this_point, base;
+	gdouble scale;
+	
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+   
+	for (size = 0; 
+			size < sizeof(_font_scale) / sizeof(_font_scale[0]);
+			++size) {
+		scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+		this_point = (guint)msim_round(scale * base);
+
+		if (this_point >= point) {
+			purple_debug_info("msim", "msim_point_to_purple_size: %d pt -> size=%d\n",
+					point, size);
+			return size;
+		}
+	}
+
+	/* No HTML font size was this big; return largest possible. */
+	return this_point;
+}
+
+/** Convert HTML font size to point size. */
+static guint
+msim_purple_size_to_point(MsimSession *session, guint size)
+{
+	gdouble scale;
+	guint point;
+	guint base;
+
+	scale = _font_scale[CLAMP(size, 1, MAX_FONT_SIZE) - 1];
+
+	base = purple_account_get_int(session->account, "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
+
+	point = (guint)msim_round(scale * base);
+
+	purple_debug_info("msim", "msim_purple_size_to_point: size=%d -> %d pt\n",
+					size, point);
+
+	return point;
+}
+
+/** Convert a msim markup font pixel height to the more usual point size, for incoming messages. */
+static guint 
+msim_height_to_point(MsimSession *session, guint height)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((POINTS_PER_INCH * 1. / dpi) * height);
+
+	/* See also: libpurple/protocols/bonjour/jabber.c
+	 * _font_size_ichat_to_purple */
+}
+
+/** Convert point size to msim pixel height font size specification, for outgoing messages. */
+static guint
+msim_point_to_height(MsimSession *session, guint point)
+{
+	guint dpi;
+
+	dpi = purple_account_get_int(session->account, "port", MSIM_DEFAULT_DPI);
+
+	return (guint)msim_round((dpi * 1. / POINTS_PER_INCH) * point);
+}
+
+/** Convert the msim markup <f> (font) tag into HTML. */
+static void 
+msim_markup_f_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *face, *height_str, *decor_str;
+	GString *gs_end, *gs_begin;
+	guint decor, height;
+
+	face = xmlnode_get_attrib(root, "f");
+	height_str = xmlnode_get_attrib(root, "h");
+	decor_str = xmlnode_get_attrib(root, "s");
+
+	if (height_str) {
+		height = atol(height_str);
+	} else {
+		height = 12;
+	}
+
+	if (decor_str) {
+		decor = atol(decor_str);
+	} else {
+		decor = 0;
+	}
+
+	gs_begin = g_string_new("");
+	/* TODO: get font size working */
+	if (height && !face) {
+		g_string_printf(gs_begin, "<font size='%d'>", 
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else if (height && face) {
+		g_string_printf(gs_begin, "<font face='%s' size='%d'>", face,  
+				msim_point_to_purple_size(session, msim_height_to_point(session, height)));
+	} else {
+		g_string_printf(gs_begin, "<font>");
+	}
+
+	/* No support for font-size CSS? */
+	/* g_string_printf(gs_begin, "<span style='font-family: %s; font-size: %dpt'>", face, 
+			msim_height_to_point(height)); */
+
+	gs_end = g_string_new("</font>");
+
+	if (decor & MSIM_TEXT_BOLD) {
+		g_string_append(gs_begin, "<b>");
+		g_string_prepend(gs_end, "</b>");
+	}
+
+	if (decor & MSIM_TEXT_ITALIC) {
+		g_string_append(gs_begin, "<i>");
+		g_string_append(gs_end, "</i>");
+	}
+
+	if (decor & MSIM_TEXT_UNDERLINE) {
+		g_string_append(gs_begin, "<u>");
+		g_string_append(gs_end, "</u>");
+	}
+
+
+	*begin = gs_begin->str;
+	*end = gs_end->str;
+}
+
+/** Convert a msim markup color to a color suitable for libpurple.
+  *
+  * @param msim Either a color name, or an rgb(x,y,z) code.
+  *
+  * @return A new string, either a color name or #rrggbb code. Must g_free(). 
+  */
+static char *
+msim_color_to_purple(const char *msim)
+{
+	guint red, green, blue;
+
+	if (!msim) {
+		return g_strdup("black");
+	}
+
+	if (sscanf(msim, "rgb(%d,%d,%d)", &red, &green, &blue) != 3) {
+		/* Color name. */
+		return g_strdup(msim);
+	}
+	/* TODO: rgba (alpha). */
+
+	return g_strdup_printf("#%.2x%.2x%.2x", red, green, blue);
+}
+
+/** Convert the msim markup <a> (anchor) tag into HTML. */
+static void 
+msim_markup_a_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *href;
+
+	href = xmlnode_get_attrib(root, "h");
+	if (!href) {
+		href = "";
+	}
+
+	*begin = g_strdup_printf("<a href=\"%s\">%s", href, href);
+	*end = g_strdup("</a>");
+}
+
+/** Convert the msim markup <p> (paragraph) tag into HTML. */
+static void 
+msim_markup_p_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	/* Just pass through unchanged. 
+	 *
+	 * Note: attributes currently aren't passed, if there are any. */
+	*begin = g_strdup("<p>");
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <c> tag (text color) into HTML. TODO: Test */
+static void 
+msim_markup_c_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		purple_debug_info("msim", "msim_markup_c_to_html: <c> tag w/o v attr\n");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	*begin = g_strdup_printf("<font color='%s'>", purple_color); 
+
+	g_free(purple_color);
+
+	/* *begin = g_strdup_printf("<span style='color: %s'>", color); */
+	*end = g_strdup("</font>");
+}
+
+/** Convert the msim markup <b> tag (background color) into HTML. TODO: Test */
+static void 
+msim_markup_b_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *color;
+	gchar *purple_color;
+
+	color = xmlnode_get_attrib(root, "v");
+	if (!color) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		purple_debug_info("msim", "msim_markup_b_to_html: <b> w/o v attr\n");
+		/* TODO: log as unrecognized. */
+		return;
+	}
+
+	purple_color = msim_color_to_purple(color);
+
+	/* TODO: find out how to set background color. */
+	*begin = g_strdup_printf("<span style='background-color: %s'>", 
+			purple_color);
+	g_free(purple_color);
+
+	*end = g_strdup("</p>");
+}
+
+/** Convert the msim markup <i> tag (emoticon image) into HTML. */
+static void 
+msim_markup_i_to_html(MsimSession *session, xmlnode *root, gchar **begin, gchar **end)
+{
+	const gchar *name;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	name = xmlnode_get_attrib(root, "n");
+	if (!name) {
+		purple_debug_info("msim", "msim_markup_i_to_html: <i> w/o n\n");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+		/* TODO: log as unrecognized */
+		return;
+	}
+
+	/* Find and use canonical form of smiley symbol. */
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		if (g_str_equal(name, emote->name)) {
+			*begin = g_strdup(emote->symbol);
+			*end = g_strdup("");
+			return;
+		}
+	}
+
+	/* Couldn't find it, sorry. Try to degrade gracefully. */
+	*begin = g_strdup_printf("**%s**", name);
+	*end = g_strdup("");
+}
+
+/** Convert an individual msim markup tag to HTML. */
+static void 
+msim_markup_tag_to_html(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	if (g_str_equal(root->name, "f")) {
+		msim_markup_f_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "a")) {
+		msim_markup_a_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "p")) {
+		msim_markup_p_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "c")) {
+		msim_markup_c_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "b")) {
+		msim_markup_b_to_html(session, root, begin, end);
+	} else if (g_str_equal(root->name, "i")) {
+		msim_markup_i_to_html(session, root, begin, end);
+	} else {
+		purple_debug_info("msim", "msim_markup_tag_to_html: "
+				"unknown tag name=%s, ignoring", 
+				(root && root->name) ? root->name : "(NULL)");
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	}
+}
+
+/** Convert an individual HTML tag to msim markup. */
+static void 
+html_tag_to_msim_markup(MsimSession *session, xmlnode *root, gchar **begin, 
+		gchar **end)
+{
+	/* TODO: Coalesce nested tags into one <f> tag!
+	 * Currently, the 's' value will be overwritten when b/i/u is nested
+	 * within another one, and only the inner-most formatting will be 
+	 * applied to the text. */
+	if (!purple_utf8_strcasecmp(root->name, "root")) {
+		*begin = g_strdup("");
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "b")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_BOLD);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "i")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_ITALIC);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "u")) {
+		*begin = g_strdup_printf("<f s='%d'>", MSIM_TEXT_UNDERLINE);
+		*end = g_strdup("</f>");
+	} else if (!purple_utf8_strcasecmp(root->name, "a")) {
+		const gchar *href, *link_text;
+
+		href = xmlnode_get_attrib(root, "href");
+
+		if (!href) {
+			href = xmlnode_get_attrib(root, "HREF");
+		}
+
+		link_text = xmlnode_get_data(root);
+
+		if (href) {
+			if (g_str_equal(link_text, href)) {
+				/* Purple gives us: <a href="URL">URL</a>
+				 * Translate to <a h='URL' />
+				 * Displayed as text of URL with link to URL
+				 */
+				*begin = g_strdup_printf("<a h='%s' />", href);
+			} else {
+				/* But if we get: <a href="URL">text</a>
+				 * Translate to: text: <a h='URL' />
+				 *
+				 * Because official client only supports self-closed <a>
+				 * tags; you can't change the link text.
+				 */
+				*begin = g_strdup_printf("%s: <a h='%s' />", link_text, href);
+			}
+		} else {
+			*begin = g_strdup("<a />");
+		}
+
+		/* Sorry, kid. MySpace doesn't support you within <a> tags. */
+		xmlnode_free(root->child);
+		root->child = NULL;
+
+		*end = g_strdup("");
+	} else if (!purple_utf8_strcasecmp(root->name, "font")) {
+		const gchar *size;
+		const gchar *face;
+
+		size = xmlnode_get_attrib(root, "size");
+		face = xmlnode_get_attrib(root, "face");
+
+		if (face && size) {
+			*begin = g_strdup_printf("<f f='%s' h='%d'>", face, 
+					msim_point_to_height(session,
+						msim_purple_size_to_point(session, atoi(size))));
+		} else if (face) {
+			*begin = g_strdup_printf("<f f='%s'>", face);
+		} else if (size) {
+			*begin = g_strdup_printf("<f h='%d'>", 
+					 msim_point_to_height(session,
+						 msim_purple_size_to_point(session, atoi(size))));
+		} else {
+			*begin = g_strdup("<f>");
+		}
+
+		*end = g_strdup("</f>");
+
+		/* TODO: color (bg uses <body>), emoticons */
+	} else {
+		*begin = g_strdup_printf("[%s]", root->name);
+		*end = g_strdup_printf("[/%s]", root->name);
+	}
+}
+
+/** Convert an xmlnode of msim markup or HTML to an HTML string or msim markup.
+ *
+ * @param f Function to convert tags.
+ *
+ * @return An HTML string. Caller frees.
+ */
+static gchar *
+msim_convert_xmlnode(MsimSession *session, xmlnode *root, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *node;
+	gchar *begin, *inner, *end;
+	GString *final;
+
+	if (!root || !root->name) {
+		return g_strdup("");
+	}
+
+	purple_debug_info("msim", "msim_convert_xmlnode: got root=%s\n",
+			root->name);
+
+	begin = inner = end = NULL;
+
+	final = g_string_new("");
+
+	f(session, root, &begin, &end);
+	
+	g_string_append(final, begin);
+
+	/* Loop over all child nodes. */
+	for (node = root->child; node != NULL; node = node->next) {
+		switch (node->type) {
+		case XMLNODE_TYPE_ATTRIB:
+			/* Attributes handled above. */
+			break;
+
+		case XMLNODE_TYPE_TAG:
+			/* A tag or tag with attributes. Recursively descend. */
+			inner = msim_convert_xmlnode(session, node, f);
+			g_return_val_if_fail(inner != NULL, NULL);
+
+			purple_debug_info("msim", " ** node name=%s\n", 
+					(node && node->name) ? node->name : "(NULL)");
+			break;
+	
+		case XMLNODE_TYPE_DATA:
+			/* Literal text. */
+			inner = g_new0(char, node->data_sz + 1);
+			strncpy(inner, node->data, node->data_sz);
+			inner[node->data_sz] = 0;
+
+			purple_debug_info("msim", " ** node data=%s\n", 
+					inner ? inner : "(NULL)");
+			break;
+			
+		default:
+			purple_debug_info("msim",
+					"msim_convert_xmlnode: strange node\n");
+			inner = g_strdup("");
+		}
+
+		if (inner) {
+			g_string_append(final, inner);
+		}
+	}
+
+	/* TODO: Note that msim counts each piece of text enclosed by <f> as
+	 * a paragraph and will display each on its own line. You actually have
+	 * to _nest_ <f> tags to intersperse different text in one paragraph!
+	 * Comment out this line below to see. */
+	g_string_append(final, end);
+
+	purple_debug_info("msim", "msim_markup_xmlnode_to_gtkhtml: RETURNING %s\n",
+			(final && final->str) ? final->str : "(NULL)");
+
+	return final->str;
+}
+
+/** Convert XML to something based on MSIM_XMLNODE_CONVERT. */
+static gchar *
+msim_convert_xml(MsimSession *session, const gchar *raw, MSIM_XMLNODE_CONVERT f)
+{
+	xmlnode *root;
+	gchar *str;
+	gchar *enclosed_raw;
+
+	g_return_val_if_fail(raw != NULL, NULL);
+
+	/* Enclose text in one root tag, to try to make it valid XML for parsing. */
+	enclosed_raw = g_strconcat("<root>", raw, "</root>", NULL);
+
+	root = xmlnode_from_str(enclosed_raw, -1);
+
+	if (!root) {
+		purple_debug_info("msim", "msim_markup_to_html: couldn't parse "
+				"%s as XML, returning raw: %s\n", enclosed_raw, raw);
+		/* TODO: msim_unrecognized */
+		g_free(enclosed_raw);
+		return g_strdup(raw);
+	}
+
+	g_free(enclosed_raw);
+
+	str = msim_convert_xmlnode(session, root, f);
+	g_return_val_if_fail(str != NULL, NULL);
+	purple_debug_info("msim", "msim_markup_to_html: returning %s\n", str);
+
+	xmlnode_free(root);
+
+	return str;
+}
+
+/** Convert plaintext smileys to <i> markup tags.
+ *
+ * @param before Original text with ASCII smileys. Will be freed.
+ * @return A new string with <i> tags, if applicable. Must be g_free()'d.
+ */
+static gchar *
+msim_convert_smileys_to_markup(gchar *before)
+{
+	gchar *old, *new, *replacement;
+	guint i;
+	struct MSIM_EMOTICON *emote;
+
+	old = before;
+	new = NULL;
+
+	for (i = 0; (emote = &msim_emoticons[i]) && emote->name != NULL; ++i) {
+		gchar *name, *symbol;
+
+		name = emote->name;
+		symbol = emote->symbol;
+
+		replacement = g_strdup_printf("<i n=\"%s\"/>", name);
+
+		purple_debug_info("msim", "msim_convert_smileys_to_markup: %s->%s\n",
+				symbol ? symbol : "(NULL)", 
+				replacement ? replacement : "(NULL)");
+		new = purple_strreplace(old, symbol, replacement);
+		
+		g_free(replacement);
+		g_free(old);
+
+		old = new;
+	}
+
+	return new;
+}
+	
+
+/** High-level function to convert MySpaceIM markup to Purple (HTML) markup. 
+ *
+ * @return Purple markup string, must be g_free()'d. */
+gchar *
+msim_markup_to_html(MsimSession *session, const gchar *raw)
+{
+	return msim_convert_xml(session, raw, 
+			(MSIM_XMLNODE_CONVERT)(msim_markup_tag_to_html));
+}
+
+/** High-level function to convert Purple (HTML) to MySpaceIM markup.
+ *
+ * TODO: consider using purple_markup_html_to_xhtml() to make valid XML.
+ *
+ * @return HTML markup string, must be g_free()'d. */
+gchar *
+html_to_msim_markup(MsimSession *session, const gchar *raw)
+{
+	gchar *markup;
+
+	markup = msim_convert_xml(session, raw,
+			(MSIM_XMLNODE_CONVERT)(html_tag_to_msim_markup));
+	
+	if (purple_account_get_bool(session->account, "emoticons", TRUE)) {
+		/* Frees markup and allocates a new one. */
+		markup = msim_convert_smileys_to_markup(markup);
+	}
+
+	return markup;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/markup.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,27 @@
+/* MySpaceIM Protocol Plugin - markup
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * 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
+ */
+
+#ifndef _MYSPACE_MARKUP_H
+#define _MYSPACE_MARKUP_H
+
+/* High-level msim markup <=> Purple html conversion functions. */
+gchar *msim_markup_to_html(MsimSession *, const gchar *raw);
+gchar *html_to_msim_markup(MsimSession *, const gchar *raw);
+
+#endif /* !_MYSPACE_MARKUP_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/message.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,1358 @@
+/** MySpaceIM protocol messages
+ *
+ * \author Jeff Connelly
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * 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 "myspace.h"
+#include "message.h"
+
+static void msim_msg_free_element(gpointer data, gpointer user_data);
+static void msim_msg_debug_string_element(gpointer data, gpointer user_data);
+static gchar *msim_msg_pack_using(MsimMessage *msg, GFunc gf, const gchar *sep, const gchar *begin, const gchar *end);
+static GList *msim_msg_get_node(MsimMessage *msg, const gchar *name);
+static MsimMessage *msim_msg_new_v(gchar *first_key, va_list argp);
+
+/* Escape codes and associated replacement text, used for protocol message
+ * escaping and unescaping. */
+static struct MSIM_ESCAPE_REPLACEMENT {
+	gchar *code;
+	gchar text;
+} msim_escape_replacements[] = {
+	{ "/1", '/' },
+	{ "/2", '\\' },
+	/* { "/3", "|" }, */      /* Not used here -- only for within arrays */
+	{ NULL, 0 }
+};
+
+/**
+ * Escape a protocol message.
+ *
+ * @return The escaped message. Caller must g_free().
+ */
+gchar *
+msim_escape(const gchar *msg)
+{
+	GString *gs;
+	guint i, j;
+
+	gs = g_string_new("");
+
+
+	for (i = 0; i < strlen(msg); ++i) {
+		struct MSIM_ESCAPE_REPLACEMENT *replacement;
+		gchar *replace;
+
+		replace = NULL;
+
+		/* Check for characters that need to be escaped, and escape them. */
+		for (j = 0; (replacement = &msim_escape_replacements[j]) &&
+				replacement->code != NULL; ++j) {
+			if (msg[i] == replacement->text) {
+				replace = replacement->code;
+				break;
+			}
+		}
+
+		if (replace) {
+			g_string_append(gs, replace);
+		} else {
+			g_string_append_c(gs, msg[i]);
+		}
+	}
+
+#ifdef MSIM_DEBUG_ESCAPE
+	purple_debug_info("msim", "msim_escape: msg=%s, ret=%s\n", msg, gs->str);
+#endif
+
+	return gs->str;
+}
+
+/**
+ * Unescape a protocol message.
+ *
+ * @return The unescaped message, caller must g_free().
+ */
+gchar *
+msim_unescape(const gchar *msg)
+{
+	GString *gs;
+	guint i, j;
+
+	gs = g_string_new("");
+
+	for (i = 0; i < strlen(msg); ++i) {
+		struct MSIM_ESCAPE_REPLACEMENT *replacement;
+		gchar replace;
+
+		replace = msg[i];
+
+		for (j = 0; (replacement = &msim_escape_replacements[j]) &&
+				replacement->code != NULL; ++j) {
+			if (msg[i] == replacement->code[0] &&
+			    i + 1 < strlen(msg) &&
+			    msg[i + 1] == replacement->code[1]) {
+				replace = replacement->text;
+				++i;
+				break;
+			}
+		}
+
+		g_string_append_c(gs, replace);
+	}
+
+#ifdef MSIM_DEBUG_ESCAPE
+	purple_debug_info("msim", "msim_unescape: msg=%s, ret=%s\n", msg, gs->str);
+#endif
+
+	return gs->str;
+}
+
+/** Create a new MsimMessage. 
+ * 
+ * @param first_key The first key in the sequence, or NULL for an empty message.
+ * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. 
+ *
+ * See msim_msg_append() documentation for details on types.
+ */
+MsimMessage *
+msim_msg_new(gchar *first_key, ...)
+{
+	va_list argp;
+
+	if (first_key) {
+	va_start(argp, first_key);
+		return msim_msg_new_v(first_key, argp);
+	} else {
+		return NULL;
+	}
+}
+
+/** Create a new message from va_list and its first argument.
+ *
+ * @param first_key The first argument (a key), or NULL to take all arguments
+ *    from argp.
+ * @param argp A va_list of variadic arguments, already started with va_start(). Will be va_end()'d.
+ * @return New MsimMessage *, must be freed with msim_msg_free().
+ *
+ * For internal use - users probably want msim_msg_new() or msim_send().
+ */
+static MsimMessage *
+msim_msg_new_v(gchar *first_key, va_list argp)
+{
+	gchar *key, *value;
+	MsimMessageType type;
+	MsimMessage *msg;
+	gboolean first;
+
+	GString *gs;
+	GList *gl;
+	MsimMessage *dict;
+
+	/* Begin with an empty message. */
+	msg = NULL;
+
+	/* First parameter can be given explicitly. */
+	first = first_key != NULL;
+
+	/* Read key, type, value triplets until NULL. */
+	do {
+		if (first) {
+			key = first_key;
+			first = FALSE;
+		} else {
+			key = va_arg(argp, gchar *);
+			if (!key) {
+				break;
+			}
+		}
+
+		type = va_arg(argp, int);
+
+		/* Interpret variadic arguments. */
+		switch (type) {
+			case MSIM_TYPE_INTEGER: 
+			case MSIM_TYPE_BOOLEAN: 
+				msg = msim_msg_append(msg, key, type, GUINT_TO_POINTER(va_arg(argp, int)));
+				break;
+				
+			case MSIM_TYPE_STRING:
+				value = va_arg(argp, char *);
+
+				g_return_val_if_fail(value != NULL, FALSE);
+
+				msg = msim_msg_append(msg, key, type, value);
+				break;
+
+			case MSIM_TYPE_BINARY:
+				gs = va_arg(argp, GString *);
+
+				g_return_val_if_fail(gs != NULL, FALSE);
+
+				/* msim_msg_free() will free this GString the caller created. */
+				msg = msim_msg_append(msg, key, type, gs);
+				break;
+			
+			case MSIM_TYPE_LIST:
+				gl = va_arg(argp, GList *);
+
+				g_return_val_if_fail(gl != NULL, FALSE);
+
+				msg = msim_msg_append(msg, key, type, gl);
+				break;
+
+			case MSIM_TYPE_DICTIONARY:
+				dict = va_arg(argp, MsimMessage *);
+
+				g_return_val_if_fail(dict != NULL, FALSE);
+
+				msg = msim_msg_append(msg, key, type, dict);
+				break;
+
+			default:
+				purple_debug_info("msim", "msim_send: unknown type %d\n", type);
+				break;
+		}
+	} while(key);
+	va_end(argp);
+
+	return msg;
+}
+
+/** Perform a deep copy on a GList * of gchar * strings. Free with msim_msg_list_free(). */
+GList *
+msim_msg_list_copy(GList *old)
+{
+	GList *new_list;
+
+	new_list = NULL;
+
+	/* Deep copy (g_list_copy is shallow). Copy each string. */
+	for (; old != NULL; old = g_list_next(old)) {
+		new_list = g_list_append(new_list, g_strdup(old->data));
+	}
+
+	return new_list;
+}
+
+/** Free a GList * of MsimMessageElement *'s. */
+void
+msim_msg_list_free(GList *l)
+{
+
+	for (; l != NULL; l = g_list_next(l)) {
+		MsimMessageElement *elem;
+
+		elem = (MsimMessageElement *)l->data;
+
+		/* Note that name is almost never dynamically allocated elsewhere;
+		 * it is usually a static string, but not in lists. So cast it. */
+		g_free((gchar *)elem->name);
+		g_free(elem->data);
+		g_free(elem);
+	}
+	g_list_free(l);
+}
+
+/** Parse a |-separated string into a new GList. Free with msim_msg_list_free(). */
+GList *
+msim_msg_list_parse(const gchar *raw)
+{
+	gchar **array;
+	GList *list;
+	guint i;
+
+	array = g_strsplit(raw, "|", 0);
+	list = NULL;
+
+	/* TODO: escape/unescape /3 <-> | within list elements */
+	
+	for (i = 0; array[i] != NULL; ++i) {
+		MsimMessageElement *elem;
+
+		/* Freed in msim_msg_list_free() */
+		elem = g_new0(MsimMessageElement, 1);
+
+		/* Give the element a name for debugging purposes.
+		 * Not supposed to be looked up by this name; instead,
+		 * lookup the elements by indexing the array. */
+		elem->name = g_strdup_printf("(list item #%d)", i);
+		elem->type = MSIM_TYPE_RAW;
+		elem->data = g_strdup(array[i]);
+
+		list = g_list_append(list, elem);
+	}
+
+	g_strfreev(array);
+
+	return list;
+}
+
+/** Clone an individual element.
+ *
+ * @param data MsimMessageElement * to clone.
+ * @param user_data Pointer to MsimMessage * to add cloned element to.
+ */
+static void 
+msim_msg_clone_element(gpointer data, gpointer user_data)
+{
+	MsimMessageElement *elem;
+	MsimMessage **new;
+	gpointer new_data;
+				
+	GString *gs;
+	MsimMessage *dict;
+
+	elem = (MsimMessageElement *)data;
+	new = (MsimMessage **)user_data;
+
+	switch (elem->type) {
+		case MSIM_TYPE_BOOLEAN:
+		case MSIM_TYPE_INTEGER:
+			new_data = elem->data;
+			break;
+
+		case MSIM_TYPE_RAW:
+		case MSIM_TYPE_STRING:
+			new_data = g_strdup((gchar *)elem->data);
+			break;
+
+		case MSIM_TYPE_LIST:
+			new_data = (gpointer)msim_msg_list_copy((GList *)(elem->data));
+			break;
+
+		case MSIM_TYPE_BINARY:
+			gs = (GString *)elem->data;
+
+			new_data = g_string_new_len(gs->str, gs->len);
+			break;
+		case MSIM_TYPE_DICTIONARY:
+			dict = (MsimMessage *)elem->data;
+
+			new_data = msim_msg_clone(dict);
+			break;
+
+		default:
+			purple_debug_info("msim", "msim_msg_clone_element: unknown type %d\n", elem->type);
+			g_return_if_fail(NULL);
+	}
+
+	/* Append cloned data. Note that the 'name' field is a static string, so it
+	 * never needs to be copied nor freed. */
+	*new = msim_msg_append(*new, elem->name, elem->type, new_data);
+}
+
+/** Clone an existing MsimMessage. 
+ *
+ * @return Cloned message; caller should free with msim_msg_free().
+ */
+MsimMessage *
+msim_msg_clone(MsimMessage *old)
+{
+	MsimMessage *new;
+
+	if (old == NULL) {
+		return NULL;
+	}
+
+	new = msim_msg_new(FALSE);
+
+	g_list_foreach(old, msim_msg_clone_element, &new);
+
+	return new;
+}
+
+/** Free the data of a message element.
+ *
+ * @param elem The MsimMessageElement *
+ *
+ * Note this only frees the element data; you may also want to free the
+ * element itself with g_free() (see msim_msg_free_element()).
+ */
+void
+msim_msg_free_element_data(MsimMessageElement *elem)
+{
+	switch (elem->type) {
+		case MSIM_TYPE_BOOLEAN:
+		case MSIM_TYPE_INTEGER:
+			/* Integer value stored in gpointer - no need to free(). */
+			break;
+
+		case MSIM_TYPE_RAW:
+		case MSIM_TYPE_STRING:
+			/* Always free strings - caller should have g_strdup()'d if
+			 * string was static or temporary and not to be freed. */
+			g_free(elem->data);
+			break;
+
+		case MSIM_TYPE_BINARY:
+			/* Free the GString itself and the binary data. */
+			g_string_free((GString *)elem->data, TRUE);
+			break;
+
+		case MSIM_TYPE_DICTIONARY:
+			msim_msg_free((MsimMessage *)elem->data);
+			break;
+			
+		case MSIM_TYPE_LIST:
+			g_list_free((GList *)elem->data);
+			break;
+
+		default:
+			purple_debug_info("msim", "msim_msg_free_element_data: "
+					"not freeing unknown type %d\n", elem->type);
+			break;
+	}
+}
+
+/** Free an individual message element.
+ *
+ * @param data MsimMessageElement * to free.
+ * @param user_data Not used; required to match g_list_foreach() callback prototype.
+ *
+ * Frees both the element data and the element itself.
+ */
+static void 
+msim_msg_free_element(gpointer data, gpointer user_data)
+{
+	MsimMessageElement *elem;
+
+	elem = (MsimMessageElement *)data;
+
+	msim_msg_free_element_data(elem);
+
+	g_free(elem);
+}
+
+/** Free a complete message. */
+void 
+msim_msg_free(MsimMessage *msg)
+{
+	if (!msg) {
+		/* already free as can be */
+		return;
+	}
+
+#ifdef MSIM_MSG_DEBUG_FREE
+	msim_msg_dump("msim_msg_free: freeing %s", msg);
+#endif
+
+	g_list_foreach(msg, msim_msg_free_element, NULL);
+	g_list_free(msg);
+}
+
+/** Send an existing MsimMessage. */
+gboolean 
+msim_msg_send(MsimSession *session, MsimMessage *msg)
+{
+	gchar *raw;
+	gboolean success;
+	
+	raw = msim_msg_pack(msg);
+	g_return_val_if_fail(raw != NULL, FALSE);
+	success = msim_send_raw(session, raw);
+	g_free(raw);
+
+	msim_msg_dump("msim_msg_send()ing %s\n", msg);
+	
+	return success;
+}
+
+/**
+ *
+ * Send a message to the server, whose contents is specified using 
+ * variable arguments.
+ *
+ * @param session
+ * @param ... A sequence of gchar* key/type/value triplets, terminated with NULL. 
+ *
+ * This function exists for coding convenience: it allows a message to be created
+ * and sent in one line of code. Internally it calls msim_msg_send(). 
+ *
+ * IMPORTANT: See msim_msg_append() documentation for details on element types.
+ *
+ */
+gboolean 
+msim_send(MsimSession *session, ...)
+{
+	gboolean success;
+	MsimMessage *msg;
+	va_list argp;
+	
+	va_start(argp, session);
+	msg = msim_msg_new_v(NULL, argp);
+
+	/* Actually send the message. */
+	success = msim_msg_send(session, msg);
+
+	/* Cleanup. */
+	msim_msg_free(msg);
+
+	return success;
+}
+
+/** Create a new MsimMessageElement * - must be g_free()'d. 
+ *
+ * For internal use; users probably want msim_msg_append() or msim_msg_insert_before(). 
+ */
+static MsimMessageElement *
+msim_msg_element_new(const gchar *name, MsimMessageType type, gpointer data)
+{
+	MsimMessageElement *elem;
+
+	elem = g_new0(MsimMessageElement, 1);
+
+	elem->name = name;
+	elem->type = type;
+	elem->data = data;
+
+	return elem;
+}
+
+
+/** Append a new element to a message. 
+ *
+ * @param name Textual name of element (static string, neither copied nor freed).
+ * @param type An MSIM_TYPE_* code.
+ * @param data Pointer to data, see below.
+ *
+ * @return The new message - must be assigned to as with GList*. For example:
+ *
+ *     msg = msim_msg_append(msg, ...)
+ *
+ * The data parameter depends on the type given:
+ *
+ * * MSIM_TYPE_INTEGER: Use GUINT_TO_POINTER(x).
+ *
+ * * MSIM_TYPE_BINARY: Same as integer, non-zero is TRUE and zero is FALSE.
+ *
+ * * MSIM_TYPE_STRING: gchar *. The data WILL BE FREED - use g_strdup() if needed.
+ *
+ * * MSIM_TYPE_RAW: gchar *. The data WILL BE FREED - use g_strdup() if needed.
+ *
+ * * MSIM_TYPE_BINARY: g_string_new_len(data, length). The data AND GString will be freed.
+ *
+ * * MSIM_TYPE_DICTIONARY: An MsimMessage *. Freed when message is destroyed.
+ *
+ * * MSIM_TYPE_LIST: GList * of gchar *. Again, everything will be freed.
+ *
+ * */
+MsimMessage *
+msim_msg_append(MsimMessage *msg, const gchar *name, 
+		MsimMessageType type, gpointer data)
+{
+	return g_list_append(msg, msim_msg_element_new(name, type, data));
+}
+
+/** Insert a new element into a message, before the given element name.
+ *
+ * @param name_before Name of the element to insert the new element before. If 
+ *                    could not be found or NULL, new element will be inserted at end.
+ *
+ * See msim_msg_append() for usage of other parameters, and an important note about return value.
+ */
+MsimMessage *
+msim_msg_insert_before(MsimMessage *msg, const gchar *name_before, 
+		const gchar *name, MsimMessageType type, gpointer data)
+{
+	MsimMessageElement *new_elem;
+	GList *node_before;
+
+	new_elem = msim_msg_element_new(name, type, data);
+
+	node_before = msim_msg_get_node(msg, name_before);
+
+	return g_list_insert_before(msg, node_before, new_elem);
+}
+
+/** Pack a string using the given GFunc and seperator.
+ * Used by msim_msg_dump() and msim_msg_pack().
+ */
+gchar *
+msim_msg_pack_using(MsimMessage *msg, 
+		GFunc gf, 
+		const gchar *sep, 
+		const gchar *begin, const gchar *end)
+{
+	gchar **strings;
+	gchar **strings_tmp;
+	gchar *joined;
+	gchar *final;
+	int i;
+
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	/* Add one for NULL terminator for g_strjoinv(). */
+	strings = (gchar **)g_new0(gchar *, g_list_length(msg) + 1);
+
+	strings_tmp = strings;
+	g_list_foreach(msg, gf, &strings_tmp);
+
+	joined = g_strjoinv(sep, strings);
+	final = g_strconcat(begin, joined, end, NULL);
+	g_free(joined);
+
+	/* Clean up. */
+	for (i = 0; i < g_list_length(msg); ++i) {
+		g_free(strings[i]);
+	}
+
+	g_free(strings);
+
+	return final;
+}
+/** Store a human-readable string describing the element.
+ *
+ * @param data Pointer to an MsimMessageElement.
+ * @param user_data 
+ */
+static void 
+msim_msg_debug_string_element(gpointer data, gpointer user_data)
+{
+	MsimMessageElement *elem;
+	gchar *string;
+	GString *gs;
+	gchar *binary;
+	gchar ***items;  /* wow, a pointer to a pointer to a pointer */
+	
+	gchar *s;
+	GList *gl;
+	guint i;
+
+	elem = (MsimMessageElement *)data;
+	items = user_data;
+
+	switch (elem->type) {
+		case MSIM_TYPE_INTEGER:
+			string = g_strdup_printf("%s(integer): %d", elem->name, 
+					GPOINTER_TO_UINT(elem->data));
+			break;
+
+		case MSIM_TYPE_RAW:
+			string = g_strdup_printf("%s(raw): %s", elem->name, 
+					elem->data ? (gchar *)elem->data : "(NULL)");
+			break;
+
+		case MSIM_TYPE_STRING:
+			string = g_strdup_printf("%s(string): %s", elem->name, 
+					elem->data ? (gchar *)elem->data : "(NULL)");
+			break;
+
+		case MSIM_TYPE_BINARY:
+			gs = (GString *)elem->data;
+			binary = purple_base64_encode((guchar*)gs->str, gs->len);
+			string = g_strdup_printf("%s(binary, %d bytes): %s", elem->name, (int)gs->len, binary);
+			g_free(binary);
+			break;
+
+		case MSIM_TYPE_BOOLEAN:
+			string = g_strdup_printf("%s(boolean): %s", elem->name,
+					elem->data ? "TRUE" : "FALSE");
+			break;
+
+		case MSIM_TYPE_DICTIONARY:
+			if (!elem->data) {
+				s = g_strdup("(NULL)");
+			} else {
+				s = msim_msg_dump_to_str((MsimMessage *)elem->data);
+			}
+
+			if (!s) {
+				s = g_strdup("(NULL, couldn't msim_msg_dump_to_str)");
+			}
+
+			string = g_strdup_printf("%s(dict): %s", elem->name, s);
+
+			g_free(s);
+			break;
+			
+		case MSIM_TYPE_LIST:
+			gs = g_string_new("");
+			g_string_append_printf(gs, "%s(list): \n", elem->name);
+
+			i = 0;
+			for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl)) {
+				g_string_append_printf(gs, " %d. %s\n", i, (gchar *)(gl->data));
+				++i;
+			}
+			
+			string = gs->str;
+			break;
+
+		default:
+			string = g_strdup_printf("%s(unknown type %d", 
+					elem->name ? elem->name : "(NULL)", elem->type);
+			break;
+	}
+
+	**items = string;
+	++(*items);
+}
+
+/** Print a human-readable string of the message to Purple's debug log.
+ *
+ * @param fmt_string A static string, in which '%s' will be replaced.
+ */
+void 
+msim_msg_dump(const gchar *fmt_string, MsimMessage *msg)
+{
+	gchar *debug_str;
+
+	g_return_if_fail(fmt_string != NULL);
+
+	debug_str = msim_msg_dump_to_str(msg);
+	
+	g_return_if_fail(debug_str != NULL);
+
+	purple_debug_info("msim", fmt_string, debug_str);
+
+	g_free(debug_str);
+}
+
+/** Return a human-readable string of the message.
+ *
+ * @return A new gchar *, must be g_free()'d.
+ */
+gchar *
+msim_msg_dump_to_str(MsimMessage *msg)
+{
+	gchar *debug_str;
+
+	if (!msg) {
+		debug_str = g_strdup("<MsimMessage: empty>");
+	} else {
+		debug_str = msim_msg_pack_using(msg, msim_msg_debug_string_element, 
+				"\n", "<MsimMessage: \n", "\n/MsimMessage>");
+	}
+
+	return debug_str;
+}
+
+/** Return a message element data as a new string for a raw protocol message, converting from other types (integer, etc.) if necessary.
+ *
+ * @return const gchar * The data as a string, or NULL. Caller must g_free().
+ *
+ * Returns a string suitable for inclusion in a raw protocol message, not necessarily
+ * optimal for human consumption. For example, strings are escaped. Use 
+ * msim_msg_get_string() if you want a string, which in some cases is same as this.
+ */
+gchar *
+msim_msg_pack_element_data(MsimMessageElement *elem)
+{
+	GString *gs;
+	GList *gl;
+
+	g_return_val_if_fail(elem != NULL, NULL);
+
+	switch (elem->type) {
+		case MSIM_TYPE_INTEGER:
+			return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data));
+
+		case MSIM_TYPE_RAW:
+			/* Not un-escaped - this is a raw element, already escaped if necessary. */
+			return (gchar *)g_strdup((gchar *)elem->data);
+
+		case MSIM_TYPE_STRING:
+			/* Strings get escaped. msim_escape() creates a new string. */
+			g_return_val_if_fail(elem->data != NULL, NULL);
+			return elem->data ? msim_escape((gchar *)elem->data) :
+				g_strdup("(NULL)");
+
+		case MSIM_TYPE_BINARY:
+			gs = (GString *)elem->data;
+			/* Do not escape! */
+			return purple_base64_encode((guchar *)gs->str, gs->len);
+
+		case MSIM_TYPE_BOOLEAN:
+			/* Not used by messages in the wire protocol * -- see msim_msg_pack_element.
+			 * Only used by dictionaries, see msim_msg_pack_element_dict. */
+			return elem->data ? g_strdup("On") : g_strdup("Off");
+
+		case MSIM_TYPE_DICTIONARY:
+			return msim_msg_pack_dict((MsimMessage *)elem->data);
+			
+		case MSIM_TYPE_LIST:
+			/* Pack using a|b|c|d|... */
+			gs = g_string_new("");
+
+			for (gl = (GList *)elem->data; gl != NULL; gl = g_list_next(gl)) {
+				g_string_append_printf(gs, "%s", (gchar*)(gl->data));
+				
+				/* All but last element is separated by a bar. */
+				if (g_list_next(gl)) 
+					g_string_append(gs, "|");
+			}
+			
+			return gs->str;
+
+		default:
+			purple_debug_info("msim", "field %s, unknown type %d\n", 
+					elem->name ? elem->name : "(NULL)", 
+					elem->type);
+			return NULL;
+	}
+}
+
+/** Pack an element into its protcol representation inside a dictionary.
+ *
+ * See msim_msg_pack_element().
+ */
+static void
+msim_msg_pack_element_dict(gpointer data, gpointer user_data)
+{
+	MsimMessageElement *elem;
+	gchar *string, *data_string, ***items;
+
+	elem = (MsimMessageElement *)data;
+	items = (gchar ***)user_data;
+
+	/* Exclude elements beginning with '_' from packed protocol messages. */
+	if (elem->name[0] == '_') {
+		return;
+	}
+
+	data_string = msim_msg_pack_element_data(elem);
+
+	g_return_if_fail(data_string != NULL);
+
+	switch (elem->type) {
+		/* These types are represented by key name/value pairs (converted above). */
+		case MSIM_TYPE_INTEGER:
+		case MSIM_TYPE_RAW:
+		case MSIM_TYPE_STRING:
+		case MSIM_TYPE_BINARY:
+		case MSIM_TYPE_DICTIONARY:
+		case MSIM_TYPE_LIST:
+		case MSIM_TYPE_BOOLEAN: /* Boolean is On or Off */
+			string = g_strconcat(elem->name, "=", data_string, NULL);
+			break;
+
+		default:
+			g_free(data_string);
+			g_return_if_fail(FALSE);
+			break;
+	}
+
+	g_free(data_string);
+
+	**items = string;
+	++(*items);
+}
+
+/** Pack an element into its protocol representation. 
+ *
+ * @param data Pointer to an MsimMessageElement.
+ * @param user_data Pointer to a gchar ** array of string items.
+ *
+ * Called by msim_msg_pack(). Will pack the MsimMessageElement into
+ * a part of the protocol string and append it to the array. Caller
+ * is responsible for creating array to correct dimensions, and
+ * freeing each string element of the array added by this function.
+ */
+static void 
+msim_msg_pack_element(gpointer data, gpointer user_data)
+{
+	MsimMessageElement *elem;
+	gchar *string, *data_string;
+	gchar ***items;
+
+	elem = (MsimMessageElement *)data;
+	items = (gchar ***)user_data;
+
+	/* Exclude elements beginning with '_' from packed protocol messages. */
+	if (elem->name[0] == '_') {
+		return;
+	}
+
+	data_string = msim_msg_pack_element_data(elem);
+
+	switch (elem->type) {
+		/* These types are represented by key name/value pairs (converted above). */
+		case MSIM_TYPE_INTEGER:
+		case MSIM_TYPE_RAW:
+		case MSIM_TYPE_STRING:
+		case MSIM_TYPE_BINARY:
+		case MSIM_TYPE_DICTIONARY:
+		case MSIM_TYPE_LIST:
+			string = g_strconcat(elem->name, "\\", data_string, NULL);
+			break;
+
+		/* Boolean is represented by absence or presence of name. */
+		case MSIM_TYPE_BOOLEAN:
+			if (GPOINTER_TO_UINT(elem->data)) {
+				/* True - leave in, with blank value. */
+				string = g_strdup_printf("%s\\", elem->name);
+			} else {
+				/* False - leave out. */
+				string = g_strdup("");
+			}
+			break;
+
+		default:
+			g_free(data_string);
+			g_return_if_fail(FALSE);
+			break;
+	}
+
+	g_free(data_string);
+
+	**items = string;
+	++(*items);
+}
+
+
+/** Return a packed string of a message suitable for sending over the wire.
+ *
+ * @return A string. Caller must g_free().
+ */
+gchar *
+msim_msg_pack(MsimMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msim_msg_pack_using(msg, msim_msg_pack_element, "\\", "\\", "\\final\\");
+}
+
+/** Return a packed string of a dictionary, suitable for embedding in MSIM_TYPE_DICTIONARY.
+ *
+ * @return A string; caller must g_free().
+ */
+gchar *
+msim_msg_pack_dict(MsimMessage *msg)
+{
+	g_return_val_if_fail(msg != NULL, NULL);
+
+	return msim_msg_pack_using(msg, msim_msg_pack_element_dict, "\034", "", "");
+}
+
+/** 
+ * Parse a raw protocol message string into a MsimMessage *.
+ *
+ * @param raw The raw message string to parse, will be g_free()'d.
+ *
+ * @return MsimMessage *. Caller should msim_msg_free() when done.
+ */
+MsimMessage *
+msim_parse(gchar *raw)
+{
+	MsimMessage *msg;
+	gchar *token;
+	gchar **tokens;
+	gchar *key;
+	gchar *value;
+	int i;
+
+	g_return_val_if_fail(raw != NULL, NULL);
+
+	purple_debug_info("msim", "msim_parse: got <%s>\n", raw);
+
+	key = NULL;
+
+	/* All messages begin with a \. */
+	if (raw[0] != '\\' || raw[1] == 0) {
+		purple_debug_info("msim", "msim_parse: incomplete/bad string, "
+				"missing initial backslash: <%s>\n", raw);
+		/* XXX: Should we try to recover, and read to first backslash? */
+
+		g_free(raw);
+		return NULL;
+	}
+
+	msg = msim_msg_new(FALSE);
+
+	for (tokens = g_strsplit(raw + 1, "\\", 0), i = 0; 
+			(token = tokens[i]);
+			i++) {
+#ifdef MSIM_DEBUG_PARSE
+		purple_debug_info("msim", "tok=<%s>, i%2=%d\n", token, i % 2);
+#endif
+		if (i % 2) {
+			/* Odd-numbered ordinal is a value. */
+
+			value = token;
+		
+			/* Incoming protocol messages get tagged as MSIM_TYPE_RAW, which
+			 * represents an untyped piece of data. msim_msg_get_* will
+			 * convert to appropriate types for caller, and handle unescaping if needed. */
+			msg = msim_msg_append(msg, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value));
+#ifdef MSIM_DEBUG_PARSE
+			purple_debug_info("msim", "insert string: |%s|=|%s|\n", key, value);
+#endif
+		} else {
+			/* Even numbered indexes are key names. */
+			key = token;
+		}
+	}
+	g_strfreev(tokens);
+
+	/* Can free now since all data was copied to hash key/values */
+	g_free(raw);
+
+	return msg;
+}
+
+/** Search for and return the node in msg, matching name, or NULL. 
+ *
+ * @param msg Message to search within.
+ * @param name Field name to search for.
+ *
+ * @return The GList * node for the MsimMessageElement with the given name, or NULL if not found or name is NULL.
+ *
+ * For internal use - users probably want to use msim_msg_get() to
+ * access the MsimMessageElement *, instead of the GList * container.
+ *
+ */
+static GList *
+msim_msg_get_node(MsimMessage *msg, const gchar *name)
+{
+	GList *node;
+
+	if (!name || !msg) {
+		return NULL;
+	}
+
+	/* Linear search for the given name. O(n) but n is small. */
+	for (node = msg; node != NULL; node = g_list_next(node)) {
+		MsimMessageElement *elem;
+
+		elem = (MsimMessageElement *)node->data;
+
+		g_return_val_if_fail(elem != NULL, NULL);
+		g_return_val_if_fail(elem->name != NULL, NULL);
+
+		if (strcmp(elem->name, name) == 0) {
+			return node;
+		}
+	}
+	return NULL;
+}
+
+/** Return the first MsimMessageElement * with given name in the MsimMessage *. 
+ *
+ * @param name Name to search for.
+ *
+ * @return MsimMessageElement * matching name, or NULL.
+ *
+ * Note: useful fields of MsimMessageElement are 'data' and 'type', which
+ * you can access directly. But it is often more convenient to use
+ * another msim_msg_get_* that converts the data to what type you want.
+ */
+MsimMessageElement *
+msim_msg_get(MsimMessage *msg, const gchar *name)
+{
+	GList *node;
+
+	node = msim_msg_get_node(msg, name);
+	if (node) {
+		return (MsimMessageElement *)node->data;
+	} else {
+		return NULL;
+	}
+}
+
+/** Return the data of an element of a given name, as a string.
+ *
+ * @param name Name of element.
+ *
+ * @return gchar * The data as a string, or NULL if not found. 
+ *     Caller must g_free().
+ *
+ * Note that msim_msg_pack_element_data() is similar, but returns a string
+ * for inclusion into a raw protocol string (escaped and everything).
+ * This function unescapes the string for you, if needed.
+ */
+gchar *
+msim_msg_get_string(MsimMessage *msg, const gchar *name)
+{
+	MsimMessageElement *elem;
+
+	elem = msim_msg_get(msg, name);
+	if (!elem) {
+		return NULL;
+	}
+
+	return msim_msg_get_string_from_element(elem);
+}
+
+gchar *
+msim_msg_get_string_from_element(MsimMessageElement *elem)
+{
+	g_return_val_if_fail(elem != NULL, NULL);
+	switch (elem->type) {
+		case MSIM_TYPE_INTEGER:
+			return g_strdup_printf("%d", GPOINTER_TO_UINT(elem->data));
+
+		case MSIM_TYPE_RAW:
+			/* Raw element from incoming message - if its a string, it'll
+			 * be escaped. */
+			return msim_unescape((gchar *)elem->data);
+
+		case MSIM_TYPE_STRING:
+			/* Already unescaped. */
+			return g_strdup((gchar *)elem->data);
+
+		default:
+			purple_debug_info("msim", "msim_msg_get_string_element: type %d unknown, name %s\n",
+					elem->type, elem->name ? elem->name : "(NULL)");
+			return NULL;
+	}
+}
+
+/** Return an element as a new list. Caller frees with msim_msg_list_free(). */
+GList *
+msim_msg_get_list(MsimMessage *msg, const gchar *name)
+{
+	MsimMessageElement *elem;
+
+	elem = msim_msg_get(msg, name);
+	if (!elem) {
+		return NULL;
+	}
+
+	return msim_msg_get_list_from_element(elem);
+}
+
+GList *
+msim_msg_get_list_from_element(MsimMessageElement *elem)
+{
+	g_return_val_if_fail(elem != NULL, NULL);
+	switch (elem->type) {
+		case MSIM_TYPE_LIST:
+			return msim_msg_list_copy((GList *)elem->data);
+
+		case MSIM_TYPE_RAW:
+			return msim_msg_list_parse((gchar *)elem->data);
+
+		default:
+			purple_debug_info("msim_msg_get_list", "type %d unknown, name %s\n",
+					elem->type, elem->name ? elem->name : "(NULL)");
+			return NULL;
+	}
+}
+
+/**
+ * Parse a \x1c-separated "dictionary" of key=value pairs into a hash table.
+ *
+ * @param raw The text of the dictionary to parse. Often the
+ *                 value for the 'body' field.
+ *
+ * @return A new MsimMessage *. Must msim_msg_free() when done.
+ */
+MsimMessage *
+msim_msg_dictionary_parse(gchar *raw)
+{
+	MsimMessage *dict;
+	gchar *item;
+	gchar **items;
+	gchar **elements;
+	guint i;
+
+	g_return_val_if_fail(raw != NULL, NULL);
+
+	dict = msim_msg_new(NULL);
+ 
+	for (items = g_strsplit(raw, "\x1c", 0), i = 0; 
+		(item = items[i]);
+		i++) {
+		gchar *key, *value;
+
+		elements = g_strsplit(item, "=", 2);
+
+		key = elements[0];
+		if (!key) {
+			purple_debug_info("msim", "msim_msg_parse_dictionary(%s): null key\n", 
+					raw);
+			g_strfreev(elements);
+			break;
+		}
+
+		value = elements[1];
+		if (!value) {
+			purple_debug_info("msim", "msim_msg_parse_dictionary(%s): null value\n", 
+					raw);
+			g_strfreev(elements);
+			break;
+		}
+
+#ifdef MSIM_DEBUG_PARSE
+		purple_debug_info("msim_msg_parse_dictionary","-- %s: %s\n", key ? key : "(NULL)", 
+				value ? value : "(NULL)");
+#endif
+		/* TODO: free key; right now it is treated as static */
+		dict = msim_msg_append(dict, g_strdup(key), MSIM_TYPE_RAW, g_strdup(value));
+
+		g_strfreev(elements);
+	}
+
+	g_strfreev(items);
+
+	return dict;
+}
+
+/** Return an element as a new dictionary. Caller frees with msim_msg_free(). */
+MsimMessage *
+msim_msg_get_dictionary(MsimMessage *msg, const gchar *name)
+{
+	MsimMessageElement *elem;
+
+	elem = msim_msg_get(msg, name);
+	if (!elem) {
+		return NULL;
+	}
+
+	return msim_msg_get_dictionary_from_element(elem);
+}
+
+MsimMessage *
+msim_msg_get_dictionary_from_element(MsimMessageElement *elem)
+{
+	g_return_val_if_fail(elem != NULL, NULL);
+	switch (elem->type) {
+		case MSIM_TYPE_DICTIONARY:
+			return msim_msg_clone((MsimMessage *)elem->data);
+		
+		case MSIM_TYPE_RAW:
+			return msim_msg_dictionary_parse((gchar *)elem->data);
+
+		default:
+			purple_debug_info("msim_msg_get_dictionary", "type %d unknown, name %s\n",
+					elem->type, elem->name ? elem->name : "(NULL)");
+			return NULL;
+	}
+}
+
+/** Return the data of an element of a given name, as an unsigned integer.
+ *
+ * @param name Name of element.
+ *
+ * @return guint Numeric representation of data, or 0 if could not be converted / not found.
+ *
+ * Useful to obtain an element's data if you know it should be an integer,
+ * even if it is not stored as an MSIM_TYPE_INTEGER. MSIM_TYPE_STRING will
+ * be converted handled correctly, for example.
+ */
+guint 
+msim_msg_get_integer(MsimMessage *msg, const gchar *name)
+{
+	MsimMessageElement *elem;
+
+	elem = msim_msg_get(msg, name);
+
+	if (!elem) {
+		return 0;
+	}
+
+	return msim_msg_get_integer_from_element(elem);
+}
+
+
+guint
+msim_msg_get_integer_from_element(MsimMessageElement *elem)
+{
+	g_return_val_if_fail(elem != NULL, 0);
+	switch (elem->type) {
+		case MSIM_TYPE_INTEGER:
+			return GPOINTER_TO_UINT(elem->data);
+
+		case MSIM_TYPE_RAW:
+		case MSIM_TYPE_STRING:
+			/* TODO: find out if we need larger integers */
+			return (guint)atoi((gchar *)elem->data);
+
+		default:
+			return 0;
+	}
+}
+
+/** Return the data of an element of a given name, as a binary GString.
+ *
+ * @param binary_data A pointer to a new pointer, which will be filled in with the binary data. CALLER MUST g_free().
+ *
+ * @param binary_length A pointer to an integer, which will be set to the binary data length.
+ *
+ * @return TRUE if successful, FALSE if not.
+ */
+gboolean 
+msim_msg_get_binary(MsimMessage *msg, const gchar *name, 
+		gchar **binary_data, gsize *binary_length)
+{
+	MsimMessageElement *elem;
+				
+	elem = msim_msg_get(msg, name);
+	if (!elem) {
+		return FALSE;
+	}
+
+	return msim_msg_get_binary_from_element(elem, binary_data, binary_length);
+}
+
+gboolean
+msim_msg_get_binary_from_element(MsimMessageElement *elem, gchar **binary_data, gsize *binary_length)
+{
+	GString *gs;
+
+	g_return_val_if_fail(elem != NULL, FALSE);
+
+	switch (elem->type) {
+		case MSIM_TYPE_RAW:
+			 /* Incoming messages are tagged with MSIM_TYPE_RAW, and
+			 * converted appropriately. They can still be "strings", just they won't
+			 * be tagged as MSIM_TYPE_STRING (as MSIM_TYPE_STRING is intended to be used
+			 * by msimprpl code for things like instant messages - stuff that should be
+			 * escaped if needed). DWIM.
+			 */
+	
+			/* Previously, incoming messages were stored as MSIM_TYPE_STRING.
+			 * This was fine for integers and strings, since they can easily be
+			 * converted in msim_get_*, as desirable. However, it does not work
+			 * well for binary strings. Consider:
+			 *
+			 * If incoming base64'd elements were tagged as MSIM_TYPE_STRING.
+			 * msim_msg_get_binary() sees MSIM_TYPE_STRING, base64 decodes, returns.
+			 * everything is fine.
+			 * But then, msim_send() is called on the incoming message, which has
+			 * a base64'd MSIM_TYPE_STRING that really is encoded binary. The values
+			 * will be escaped since strings are escaped, and / becomes /2; no good.
+			 *
+			 */
+			*binary_data = (gchar *)purple_base64_decode((const gchar *)elem->data, binary_length);
+			return TRUE;
+
+		case MSIM_TYPE_BINARY:
+			gs = (GString *)elem->data;
+
+			/* Duplicate data, so caller can g_free() it. */
+			*binary_data = g_new0(char, gs->len);
+			memcpy(*binary_data, gs->str, gs->len);
+
+			*binary_length = gs->len;
+
+			return TRUE;
+
+
+			/* Rejected because if it isn't already a GString, have to g_new0 it and
+			 * then caller has to ALSO free the GString! 
+			 *
+			 * return (GString *)elem->data; */
+
+		default:
+			purple_debug_info("msim", "msim_msg_get_binary: unhandled type %d for key %s\n",
+					elem->type, elem->name ? elem->name : "(NULL)");
+			return FALSE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/message.h	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,117 @@
+/** MySpaceIM protocol messages
+ *
+ * \author Jeff Connelly
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * 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
+ */
+
+#ifndef _MYSPACE_MESSAGE_H
+#define _MYSPACE_MESSAGE_H
+
+#include <glib.h>
+
+/* Types */
+#define MsimMessage GList               /* #define instead of typedef to avoid casting */
+typedef struct _MsimMessageElement
+{
+	const gchar *name;              /**< Textual name of element. */
+	guint type;                     /**< MSIM_TYPE_* code. */
+	gpointer data;                  /**< Pointer to data, or GUINT_TO_POINTER for int/bool. */
+} MsimMessageElement;
+
+typedef gchar MsimMessageType;
+
+#define msim_msg_get_next_element_node(msg)    ((MsimMessage *)(msg->next))
+
+/* Protocol field types */
+#define MSIM_TYPE_RAW            '-'
+#define MSIM_TYPE_INTEGER        'i'
+#define MSIM_TYPE_STRING         's'
+#define MSIM_TYPE_BINARY         'b'
+#define MSIM_TYPE_BOOLEAN        'f'
+#define MSIM_TYPE_DICTIONARY     'd'
+#define MSIM_TYPE_LIST           'l'
+
+gchar *msim_escape(const gchar *msg);
+gchar *msim_unescape(const gchar *msg);
+
+MsimMessage *msim_msg_new(gchar *first_key, ...);
+/* No sentinel attribute, because can leave off varargs if not_empty is FALSE. */
+
+MsimMessage *msim_msg_clone(MsimMessage *old);
+void msim_msg_free_element_data(MsimMessageElement *elem);
+void msim_msg_free(MsimMessage *msg);
+MsimMessage *msim_msg_append(MsimMessage *msg, const gchar *name, MsimMessageType type, gpointer data);
+MsimMessage *msim_msg_insert_before(MsimMessage *msg, const gchar *name_before, const gchar *name, MsimMessageType type, gpointer data);
+gchar *msim_msg_dump_to_str(MsimMessage *msg);
+gchar *msim_msg_pack_element_data(MsimMessageElement *elem);
+void msim_msg_dump(const char *fmt_string, MsimMessage *msg);
+gchar *msim_msg_pack(MsimMessage *msg);
+gchar *msim_msg_pack_dict(MsimMessage *msg);
+
+GList *msim_msg_list_copy(GList *old);
+void msim_msg_list_free(GList *l);
+GList *msim_msg_list_parse(const gchar *raw);
+
+/* Defined in myspace.h */
+struct _MsimSession;
+
+/* Based on http://permalink.gmane.org/gmane.comp.parsers.sparse/695 
+ * Define macros for useful gcc attributes. */
+#ifdef __GNUC__
+#define GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__)
+#define FORMAT_ATTR(pos) __attribute__ ((__format__ (__printf__, pos, pos+1)))
+#define NORETURN_ATTR __attribute__ ((__noreturn__))
+/* __sentinel__ attribute was introduced in gcc 3.5 */
+#if (GCC_VERSION >= 3005)
+	#define SENTINEL_ATTR __attribute__ ((__sentinel__(0)))
+#else
+	#define SENTINEL_ATTR
+#endif /* gcc >= 3.5 */
+#else
+	#define FORMAT_ATTR(pos)
+	#define NORETURN_ATTR
+	#define SENTINEL_ATTR
+#endif 
+
+/* Cause gcc to emit "a missing sentinel in function call" if forgot
+ * to write NULL as last, terminating parameter. */
+gboolean msim_send(struct _MsimSession *session, ...) SENTINEL_ATTR;
+
+gboolean msim_msg_send(struct _MsimSession *session, MsimMessage *msg);
+
+MsimMessage *msim_parse(gchar *raw);
+MsimMessage *msim_msg_dictionary_parse(gchar *raw);
+
+MsimMessageElement *msim_msg_get(MsimMessage *msg, const gchar *name);
+
+/* Retrieve data by name */
+gchar *msim_msg_get_string(MsimMessage *msg, const gchar *name);
+GList *msim_msg_get_list(MsimMessage *msg, const gchar *name);
+MsimMessage *msim_msg_get_dictionary(MsimMessage *msg, const gchar *name);
+guint msim_msg_get_integer(MsimMessage *msg, const gchar *name);
+gboolean msim_msg_get_binary(MsimMessage *msg, const gchar *name, gchar **binary_data, gsize *binary_length);
+
+/* Retrieve data by element (MsimMessageElement *), returned from msim_msg_get() */
+gchar *msim_msg_get_string_from_element(MsimMessageElement *elem);
+GList *msim_msg_get_list_from_element(MsimMessageElement *elem);
+MsimMessage *msim_msg_get_dictionary_from_element(MsimMessageElement *elem);
+guint msim_msg_get_integer_from_element(MsimMessageElement *elem);
+gboolean msim_msg_get_binary_from_element(MsimMessageElement *elem, 
+		gchar **binary_data, gsize *binary_length);
+
+#endif /* _MYSPACE_MESSAGE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Wed Sep 12 19:11:38 2007 +0000
@@ -0,0 +1,3304 @@
+/* MySpaceIM Protocol Plugin
+ *
+ * \author Jeff Connelly
+ *
+ * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
+ *
+ * Based on Purple's "C Plugin HOWTO" hello world example.
+ *
+ * Code also drawn from mockprpl:
+ *  http://snarfed.org/space/purple+mock+protocol+plugin
+ *  Copyright (C) 2004-2007, Ryan Barrett <mockprpl@ryanb.org>
+ *
+ * and some constructs also based on existing Purple plugins, which are:
+ *   Copyright (C) 2003, Robbert Haarman <purple@inglorion.net>
+ *   Copyright (C) 2003, Ethan Blanton <eblanton@cs.purdue.edu>
+ *   Copyright (C) 2000-2003, Rob Flynn <rob@tgflinux.com>
+ *   Copyright (C) 1998-1999, Mark Spencer <markster@marko.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  02111-1301  USA
+ */
+
+#define PURPLE_PLUGIN
+
+#include "myspace.h"
+
+/* Internal functions */
+
+#ifdef MSIM_DEBUG_MSG
+static void print_hash_item(gpointer key, gpointer value, gpointer user_data);
+#endif
+
+static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes);
+static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg);
+static const gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len);
+
+static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg);
+static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg);
+static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg);
+static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg);
+/* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */
+static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg);
+static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg);
+static gboolean msim_incoming_unofficial_client(MsimSession *session, 
+		MsimMessage *msg);
+
+#ifdef MSIM_SEND_CLIENT_VERSION
+static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username);
+#endif
+
+static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+
+static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring);
+
+static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg);
+static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); 
+static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg);
+
+static gboolean msim_preprocess_incoming(MsimSession *session, MsimMessage *msg);
+
+#ifdef MSIM_USE_KEEPALIVE
+static gboolean msim_check_alive(gpointer data);
+#endif
+
+static gboolean msim_we_are_logged_on(MsimSession *session, MsimMessage *msg);
+
+static gboolean msim_process(MsimSession *session, MsimMessage *msg);
+
+static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid);
+static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); 
+
+static gboolean msim_error(MsimSession *session, MsimMessage *msg);
+
+static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static gboolean msim_check_inbox(gpointer data);
+
+static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond);
+
+
+static void msim_connect_cb(gpointer data, gint source, const gchar *error_message);
+
+static void msim_import_friends(PurplePluginAction *action);
+static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data);
+static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after);
+
+static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params);
+static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data);
+
+/** 
+ * Load the plugin.
+ */
+gboolean 
+msim_load(PurplePlugin *plugin)
+{
+	/* If compiled to use RC4 from libpurple, check if it is really there. */
+	if (!purple_ciphers_find_cipher("rc4")) {
+		purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n");
+		purple_notify_error(plugin, _("Missing Cipher"), 
+				_("The RC4 cipher could not be found"),
+				_("Upgrade "
+					"to a libpurple with RC4 support (>= 2.0.1). MySpaceIM "
+					"plugin will not be loaded."));
+		return FALSE;
+	}
+	return TRUE;
+}
+
+/**
+ * Get possible user status types. Based on mockprpl.
+ *
+ * @return GList of status types.
+ */
+GList *
+msim_status_types(PurpleAccount *acct)
+{
+	GList *types;
+	PurpleStatusType *status;
+
+	purple_debug_info("myspace", "returning status types\n");
+
+	types = NULL;
+
+    /* Statuses are almost all the same. Define a macro to reduce code repetition. */
+#define _MSIM_ADD_NEW_STATUS(prim) status =                         \
+	purple_status_type_new_with_attrs(                          \
+	prim,   /* PurpleStatusPrimitive */                         \
+	NULL,   /* id - use default */                              \
+	NULL,   /* name - use default */                            \
+	TRUE,   /* savable */                                       \
+	TRUE,   /* user_settable */                                 \
+	FALSE,  /* not independent */                               \
+	                                                            \
+	/* Attributes - each status can have a message. */          \
+	"message",                                                  \
+	_("Message"),                                               \
+	purple_value_new(PURPLE_TYPE_STRING),                       \
+	NULL);                                                      \
+	                                                            \
+	                                                            \
+	types = g_list_append(types, status)
+
+
+	_MSIM_ADD_NEW_STATUS(PURPLE_STATUS_AVAILABLE);
+	_MSIM_ADD_NEW_STATUS(PURPLE_STATUS_AWAY);
+	_MSIM_ADD_NEW_STATUS(PURPLE_STATUS_OFFLINE);
+	_MSIM_ADD_NEW_STATUS(PURPLE_STATUS_INVISIBLE);
+
+
+	return types;
+}
+
+/**
+ * Return the icon name for a buddy and account.
+ *
+ * @param acct The account to find the icon for, or NULL for protocol icon.
+ * @param buddy The buddy to find the icon for, or NULL for the account icon.
+ *
+ * @return The base icon name string.
+ */
+const gchar *
+msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
+{
+	/* Use a MySpace icon submitted by hbons at
+	 * http://developer.pidgin.im/wiki/MySpaceIM. */
+	return "myspace";
+}
+
+#ifdef MSIM_DEBUG_MSG
+static void 
+print_hash_item(gpointer key, gpointer value, gpointer user_data)
+{
+	purple_debug_info("msim", "%s=%s\n",
+			key ? (gchar *)key : "(NULL)", 
+			value ? (gchar *)value : "(NULL)");
+}
+#endif
+
+/** 
+ * Send raw data (given as a NUL-terminated string) to the server.
+ *
+ * @param session 
+ * @param msg The raw data to send, in a NUL-terminated string.
+ *
+ * @return TRUE if succeeded, FALSE if not.
+ *
+ */
+gboolean 
+msim_send_raw(MsimSession *session, const gchar *msg)
+{
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+	
+	purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg);
+
+	return msim_send_really_raw(session->gc, msg, strlen(msg)) ==
+		strlen(msg);
+}
+
+/** Send raw data to the server, possibly with embedded NULs. 
+ *
+ * Used in prpl_info struct, so that plugins can have the most possible
+ * control of what is sent over the connection. Inside this prpl, 
+ * msim_send_raw() is used, since it sends NUL-terminated strings (easier).
+ *
+ * @param gc PurpleConnection
+ * @param buf Buffer to send
+ * @param total_bytes Size of buffer to send
+ *
+ * @return Bytes successfully sent, or -1 on error.
+ */
+static int 
+msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes)
+{
+	int total_bytes_sent;
+	MsimSession *session;
+
+	g_return_val_if_fail(gc != NULL, -1);
+	g_return_val_if_fail(buf != NULL, -1);
+	g_return_val_if_fail(total_bytes >= 0, -1);
+
+	session = (MsimSession *)gc->proto_data;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
+	
+	/* Loop until all data is sent, or a failure occurs. */
+	total_bytes_sent = 0;
+	do {
+		int bytes_sent;
+
+		bytes_sent = send(session->fd, buf + total_bytes_sent, 
+				total_bytes - total_bytes_sent, 0);
+
+		if (bytes_sent < 0) {
+			purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n",
+					buf, g_strerror(errno));
+			return total_bytes_sent;
+		}
+		total_bytes_sent += bytes_sent;
+
+	} while(total_bytes_sent < total_bytes);
+
+	return total_bytes_sent;
+}
+
+
+/** 
+ * Start logging in to the MSIM servers.
+ * 
+ * @param acct Account information to use to login.
+ */
+void 
+msim_login(PurpleAccount *acct)
+{
+	PurpleConnection *gc;
+	const gchar *host;
+	int port;
+
+	g_return_if_fail(acct != NULL);
+	g_return_if_fail(acct->username != NULL);
+
+	purple_debug_info("msim", "logging in %s\n", acct->username);
+
+	gc = purple_account_get_connection(acct);
+	gc->proto_data = msim_session_new(acct);
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC;
+
+#ifdef MSIM_MAX_PASSWORD_LENGTH
+	/* Passwords are limited in length. */
+	if (strlen(acct->password) > MSIM_MAX_PASSWORD_LENGTH) {
+		gchar *str;
+
+		str = g_strdup_printf(
+				_("Sorry, passwords over %d characters in length (yours is "
+				"%d) are not supported by MySpace."), 
+				MSIM_MAX_PASSWORD_LENGTH,
+				(int)strlen(acct->password));
+
+		/* Notify an error message also, because this is important! */
+		purple_notify_error(acct, g_strdup(_("MySpaceIM Error")), str, NULL);
+
+		purple_connection_error(gc, str);
+		
+		g_free(str);
+	}
+#endif
+
+	/* 1. connect to server */
+	purple_connection_update_progress(gc, _("Connecting"),
+								  0,   /* which connection step this is */
+								  4);  /* total number of steps */
+
+	host = purple_account_get_string(acct, "server", MSIM_SERVER);
+	port = purple_account_get_int(acct, "port", MSIM_PORT);
+
+	/* From purple.sf.net/api:
+	 * """Note that this function name can be misleading--although it is called 
+	 * "proxy connect," it is used for establishing any outgoing TCP connection, 
+	 * whether through a proxy or not.""" */
+
+	/* Calls msim_connect_cb when connected. */
+	if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) {
+		/* TODO: try other ports if in auto mode, then save
+		 * working port and try that first next time. */
+		purple_connection_error(gc, _("Couldn't create socket"));
+		return;
+	}
+}
+
+/**
+ * Process a login challenge, sending a response. 
+ *
+ * @param session 
+ * @param msg Login challenge message.
+ *
+ * @return TRUE if successful, FALSE if not
+ */
+static gboolean 
+msim_login_challenge(MsimSession *session, MsimMessage *msg) 
+{
+	PurpleAccount *account;
+	const gchar *response;
+	guint response_len;
+	gchar *nc;
+	gsize nc_len;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	g_return_val_if_fail(msim_msg_get_binary(msg, "nc", &nc, &nc_len), FALSE);
+
+	account = session->account;
+
+	g_return_val_if_fail(account != NULL, FALSE);
+
+	purple_connection_update_progress(session->gc, _("Reading challenge"), 1, 4);
+
+	purple_debug_info("msim", "nc is %d bytes, decoded\n", nc_len);
+
+	if (nc_len != MSIM_AUTH_CHALLENGE_LENGTH) {
+		purple_debug_info("msim", "bad nc length: %x != 0x%x\n", nc_len, MSIM_AUTH_CHALLENGE_LENGTH);
+		purple_connection_error(session->gc, _("Unexpected challenge length from server"));
+		return FALSE;
+	}
+
+	purple_connection_update_progress(session->gc, _("Logging in"), 2, 4);
+
+	response_len = 0;
+	response = msim_compute_login_response(nc, account->username, account->password, &response_len);
+
+	g_free(nc);
+
+	return msim_send(session, 
+			"login2", MSIM_TYPE_INTEGER, MSIM_AUTH_ALGORITHM,
+			/* This is actually user's email address. */
+			"username", MSIM_TYPE_STRING, g_strdup(account->username),
+			/* GString and gchar * response will be freed in msim_msg_free() in msim_send(). */
+			"response", MSIM_TYPE_BINARY, g_string_new_len(response, response_len),
+			"clientver", MSIM_TYPE_INTEGER, MSIM_CLIENT_VERSION,
+			"langid", MSIM_TYPE_INTEGER, MSIM_LANGUAGE_ID_ENGLISH,
+			"imlang", MSIM_TYPE_STRING, g_strdup(MSIM_LANGUAGE_NAME_ENGLISH),
+			"reconn", MSIM_TYPE_INTEGER, 0,
+			"status", MSIM_TYPE_INTEGER, 100,
+			"id", MSIM_TYPE_INTEGER, 1,
+			NULL);
+}
+
+/**
+ * Compute the base64'd login challenge response based on username, password, nonce, and IPs.
+ *
+ * @param nonce The base64 encoded nonce ('nc') field from the server.
+ * @param email User's email address (used as login name).
+ * @param password User's cleartext password.
+ * @param response_len Will be written with response length.
+ *
+ * @return Binary login challenge response, ready to send to the server. 
+ * Must be g_free()'d when finished. NULL if error.
+ */
+static const gchar *
+msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], 
+		const gchar *email, const gchar *password, guint *response_len)
+{
+	PurpleCipherContext *key_context;
+	PurpleCipher *sha1;
+	PurpleCipherContext *rc4;
+
+	guchar hash_pw[HASH_SIZE];
+	guchar key[HASH_SIZE];
+	gchar *password_utf16le, *password_utf8_lc;
+	guchar *data;
+	guchar *data_out;
+	size_t data_len, data_out_len;
+	gsize conv_bytes_read, conv_bytes_written;
+	GError *conv_error;
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+	int i;
+#endif
+
+	g_return_val_if_fail(nonce != NULL, NULL);
+	g_return_val_if_fail(email != NULL, NULL);
+	g_return_val_if_fail(password != NULL, NULL);
+	g_return_val_if_fail(response_len != NULL, NULL);
+
+	/* Convert password to lowercase (required for passwords containing
+	 * uppercase characters). MySpace passwords are lowercase,
+	 * see ticket #2066. */
+	password_utf8_lc = g_utf8_strdown(password, -1);
+
+	/* Convert ASCII password to UTF16 little endian */
+	purple_debug_info("msim", "converting password to UTF-16LE\n");
+	conv_error = NULL;
+	password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", 
+			&conv_bytes_read, &conv_bytes_written, &conv_error);
+	g_free(password_utf8_lc);
+
+	g_return_val_if_fail(conv_bytes_read == strlen(password), NULL);
+
+	if (conv_error != NULL) {
+		purple_debug_error("msim", 
+				"g_convert password UTF8->UTF16LE failed: %s",
+				conv_error->message);
+		g_error_free(conv_error);
+		return NULL;
+	}
+
+	/* Compute password hash */ 
+	purple_cipher_digest_region("sha1", (guchar *)password_utf16le, 
+			conv_bytes_written, sizeof(hash_pw), hash_pw, NULL);
+	g_free(password_utf16le);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+	purple_debug_info("msim", "pwhash = ");
+	for (i = 0; i < sizeof(hash_pw); i++)
+		purple_debug_info("msim", "%.2x ", hash_pw[i]);
+	purple_debug_info("msim", "\n");
+#endif
+
+	/* key = sha1(sha1(pw) + nonce2) */
+	sha1 = purple_ciphers_find_cipher("sha1");
+	key_context = purple_cipher_context_new(sha1, NULL);
+	purple_cipher_context_append(key_context, hash_pw, HASH_SIZE);
+	purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE);
+	purple_cipher_context_digest(key_context, sizeof(key), key, NULL);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+	purple_debug_info("msim", "key = ");
+	for (i = 0; i < sizeof(key); i++) {
+		purple_debug_info("msim", "%.2x ", key[i]);
+	}
+	purple_debug_info("msim", "\n");
+#endif
+
+	rc4 = purple_cipher_context_new_by_name("rc4", NULL);
+
+	/* Note: 'key' variable is 0x14 bytes (from SHA-1 hash), 
+	 * but only first 0x10 used for the RC4 key. */
+	purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
+	purple_cipher_context_set_key(rc4, key);
+
+	/* TODO: obtain IPs of network interfaces */
+
+	/* rc4 encrypt:
+	 * nonce1+email+IP list */
+
+	data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN;
+	data = g_new0(guchar, data_len);
+	memcpy(data, nonce, NONCE_SIZE);
+	memcpy(data + NONCE_SIZE, email, strlen(email));
+	memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
+
+	data_out = g_new0(guchar, data_len);
+
+	purple_cipher_context_encrypt(rc4, (const guchar *)data, 
+			data_len, data_out, &data_out_len);
+	purple_cipher_context_destroy(rc4);
+
+	g_assert(data_out_len == data_len);
+
+#ifdef MSIM_DEBUG_LOGIN_CHALLENGE
+	purple_debug_info("msim", "response=<%s>\n", data_out);
+#endif
+
+	*response_len = data_out_len;
+
+	return (const gchar *)data_out;
+}
+
+/**
+ * Schedule an IM to be sent once the user ID is looked up. 
+ *
+ * @param gc Connection.
+ * @param who A user id, email, or username to send the message to.
+ * @param message Instant message text to send.
+ * @param flags Flags.
+ *
+ * @return 1 if successful or postponed, -1 if failed
+ *
+ * Allows sending to a user by username, email address, or userid. If
+ * a username or email address is given, the userid must be looked up.
+ * This function does that by calling msim_postprocess_outgoing().
+ */
+int 
+msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message, 
+		PurpleMessageFlags flags)
+{
+	MsimSession *session;
+	gchar *message_msim;
+	int rc;
+	
+	g_return_val_if_fail(gc != NULL, -1);
+	g_return_val_if_fail(who != NULL, -1);
+	g_return_val_if_fail(message != NULL, -1);
+
+	/* 'flags' has many options, not used here. */
+
+	session = (MsimSession *)gc->proto_data;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
+
+	message_msim = html_to_msim_markup(session, message);
+
+	if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) {
+		/* Return 1 to have Purple show this IM as being sent, 0 to not. I always
+		 * return 1 even if the message could not be sent, since I don't know if
+		 * it has failed yet--because the IM is only sent after the userid is
+		 * retrieved from the server (which happens after this function returns).
+		 */
+		/* TODO: maybe if message is delayed, don't echo to conv window,
+		 * but do echo it to conv window manually once it is actually
+		 * sent? Would be complicated. */
+		rc = 1;
+	} else {
+		rc = -1;
+	}
+
+	g_free(message_msim);
+
+	/*
+	 * In MySpace, you login with your email address, but don't talk to other
+	 * users using their email address. So there is currently an asymmetry in the 
+	 * IM windows when using this plugin:
+	 *
+	 * you@example.com: hello
+	 * some_other_user: what's going on?
+	 * you@example.com: just coding a prpl
+	 *
+	 * TODO: Make the sent IM's appear as from the user's username, instead of
+	 * their email address. Purple uses the login (in MSIM, the email)--change this.
+	 */
+
+	return rc;
+}
+
+/** Send a buddy message of a given type.
+ *
+ * @param session
+ * @param who Username to send message to.
+ * @param text Message text to send. Not freed; will be copied.
+ * @param type A MSIM_BM_* constant.
+ *
+ * @return TRUE if success, FALSE if fail.
+ *
+ * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
+ *
+ */
+gboolean 
+msim_send_bm(MsimSession *session, const gchar *who, const gchar *text, 
+		int type)
+{
+	gboolean rc;
+	MsimMessage *msg;
+	const gchar *from_username;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(who != NULL, FALSE);
+	g_return_val_if_fail(text != NULL, FALSE);
+   
+	from_username = session->account->username;
+
+	g_return_val_if_fail(from_username != NULL, FALSE);
+
+	purple_debug_info("msim", "sending %d message from %s to %s: %s\n",
+				  type, from_username, who, text);
+
+	msg = msim_msg_new(
+            "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
+			"sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
+			/* 't' will be inserted here */
+			"cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
+			"msg", MSIM_TYPE_STRING, g_strdup(text),
+			NULL);
+
+	rc = msim_postprocess_outgoing(session, msg, who, "t", "cv");
+
+	msim_msg_free(msg);
+
+	return rc;
+}
+
+
+/** Record the client version in the buddy list, from an incoming message. */
+static gboolean
+msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg)
+{
+	gchar *username, *cv;
+	gboolean ret;
+	MsimUser *user;
+
+	username = msim_msg_get_string(msg, "_username");
+	cv = msim_msg_get_string(msg, "cv");
+
+	g_return_val_if_fail(username != NULL, FALSE);
+	if (!cv) {
+		/* No client version to record, don't worry about it. */
+		return FALSE;
+	}
+
+	user = msim_find_user(session, username);
+
+	if (user) {
+		user->client_cv = atol(cv);
+		ret = TRUE;
+	} else {
+		ret = FALSE;
+	}
+
+	g_free(username);
+	g_free(cv);
+
+	return ret;
+}
+
+/** Handle an incoming buddy message. */
+static gboolean
+msim_incoming_bm(MsimSession *session, MsimMessage *msg)
+{
+	guint bm;
+   
+	bm = msim_msg_get_integer(msg, "bm");
+
+	msim_incoming_bm_record_cv(session, msg);
+
+	switch (bm) {
+		case MSIM_BM_STATUS:
+			return msim_incoming_status(session, msg);
+		case MSIM_BM_INSTANT:
+			return msim_incoming_im(session, msg);
+		case MSIM_BM_ACTION:
+			return msim_incoming_action(session, msg);
+		case MSIM_BM_MEDIA:
+			return msim_incoming_media(session, msg);
+		case MSIM_BM_UNOFFICIAL_CLIENT:
+			return msim_incoming_unofficial_client(session, msg);
+		default:
+			/* Not really an IM, but show it for informational 
+			 * purposes during development. */
+			return msim_incoming_im(session, msg);
+	}
+}
+
+/**
+ * Handle an incoming instant message.
+ *
+ * @param session The session
+ * @param msg Message from the server, containing 'f' (userid from) and 'msg'. 
+ *               Should also contain username in _username from preprocessing.
+ *
+ * @return TRUE if successful.
+ */
+static gboolean 
+msim_incoming_im(MsimSession *session, MsimMessage *msg)
+{
+	gchar *username, *msg_msim_markup, *msg_purple_markup;
+	time_t time_received;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	username = msim_msg_get_string(msg, "_username");
+	g_return_val_if_fail(username != NULL, FALSE);
+
+	msg_msim_markup = msim_msg_get_string(msg, "msg");
+	g_return_val_if_fail(msg_msim_markup != NULL, FALSE);
+
+	msg_purple_markup = msim_markup_to_html(session, msg_msim_markup);
+	g_free(msg_msim_markup);
+
+	time_received = msim_msg_get_integer(msg, "date");
+	if (!time_received) {
+		time_received = time(NULL);
+	}
+
+	serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received);
+
+	g_free(username);
+	g_free(msg_purple_markup);
+
+	return TRUE;
+}
+
+/**
+ * Process unrecognized information.
+ *
+ * @param session
+ * @param msg An MsimMessage that was unrecognized, or NULL.
+ * @param note Information on what was unrecognized, or NULL.
+ */
+void 
+msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
+{
+	/* TODO: Some more context, outwardly equivalent to a backtrace, 
+	 * for helping figure out what this msg is for. What was going on?
+	 * But not too much information so that a user
+	 * posting this dump reveals confidential information.
+	 */
+
+	/* TODO: dump unknown msgs to file, so user can send them to me
+	 * if they wish, to help add support for new messages (inspired
+	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
+
+	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
+			(session && session->account && session->account->username) ? 
+			session->account->username : "(NULL)");
+	if (note) {
+		purple_debug_info("msim", "(Note: %s)\n", note);
+	}
+
+	if (msg) {
+		msim_msg_dump("Unrecognized message dump: %s\n", msg);
+	}
+}
+
+/**
+ * Handle an incoming action message.
+ *
+ * @param session
+ * @param msg
+ *
+ * @return TRUE if successful.
+ *
+ */
+static gboolean 
+msim_incoming_action(MsimSession *session, MsimMessage *msg)
+{
+	gchar *msg_text, *username;
+	gboolean rc;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	msg_text = msim_msg_get_string(msg, "msg");
+	g_return_val_if_fail(msg_text != NULL, FALSE);
+
+	username = msim_msg_get_string(msg, "_username");
+	g_return_val_if_fail(username != NULL, FALSE);
+
+	purple_debug_info("msim", "msim_incoming_action: action <%s> from <%d>\n", 
+			msg_text, username);
+
+	if (g_str_equal(msg_text, "%typing%")) {
+		/* TODO: find out if msim repeatedly sends typing messages, so we can 
+		 * give it a timeout. Right now, there does seem to be an inordinately 
+		 * amount of time between typing stopped-typing notifications. */
+		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
+		rc = TRUE;
+	} else if (g_str_equal(msg_text, "%stoptyping%")) {
+		serv_got_typing_stopped(session->gc, username);
+		rc = TRUE;
+	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
+		rc = msim_incoming_zap(session, msg);
+	} else {
+		msim_unrecognized(session, msg, 
+				"got to msim_incoming_action but unrecognized value for 'msg'");
+		rc = FALSE;
+	}
+
+	g_free(msg_text);
+	g_free(username);
+
+	return rc;
+}
+
+/* Process an incoming media (message background?) message. */
+static gboolean
+msim_incoming_media(MsimSession *session, MsimMessage *msg)
+{
+	gchar *username, *text;
+
+	username = msim_msg_get_string(msg, "_username");
+	text = msim_msg_get_string(msg, "msg");
+
+	g_return_val_if_fail(username != NULL, FALSE);
+	g_return_val_if_fail(text != NULL, FALSE);
+
+	purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text);
+
+	/* Media messages are sent when the user opens a window to someone.
+	 * Tell libpurple they started typing and stopped typing, to inform the Psychic
+	 * Mode plugin so it too can open a window to the user. */
+	serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
+	serv_got_typing_stopped(session->gc, username);
+
+	g_free(username);
+
+	return TRUE;
+}
+
+/* Process an incoming "unofficial client" message. The plugin for
+ * Miranda IM sends this message with the plugin information. */
+static gboolean
+msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg)
+{
+	MsimUser *user;
+	gchar *username, *client_info;
+
+	username = msim_msg_get_string(msg, "_username");
+	client_info = msim_msg_get_string(msg, "msg");
+
+	g_return_val_if_fail(username != NULL, FALSE);
+	g_return_val_if_fail(client_info != NULL, FALSE);
+
+	purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n",
+		username, client_info);
+
+	user = msim_find_user(session, username);
+	
+	g_return_val_if_fail(user != NULL, FALSE);
+
+	if (user->client_info) {
+		g_free(user->client_info);
+	}
+	user->client_info = client_info;
+
+	g_free(username);
+	/* Do not free client_info - the MsimUser now owns it. */
+
+	return TRUE;
+}
+
+
+#ifdef MSIM_SEND_CLIENT_VERSION
+/** Send our client version to another unofficial client that understands it. */
+static gboolean
+msim_send_unofficial_client(MsimSession *session, gchar *username)
+{
+	gchar *our_info;
+	gboolean ret;
+
+	our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s", 
+			PURPLE_MAJOR_VERSION,
+			PURPLE_MINOR_VERSION,
+			PURPLE_MICRO_VERSION,
+			MSIM_PRPL_VERSION_STRING);
+
+	ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT);
+
+	return ret;
+}
+#endif
+
+/** 
+ * Handle when our user starts or stops typing to another user.
+ *
+ * @param gc
+ * @param name The buddy name to which our user is typing to
+ * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING
+ *
+ * @return 0
+ */
+unsigned int 
+msim_send_typing(PurpleConnection *gc, const gchar *name, 
+		PurpleTypingState state)
+{
+	const gchar *typing_str;
+	MsimSession *session;
+
+	g_return_val_if_fail(gc != NULL, 0);
+	g_return_val_if_fail(name != NULL, 0);
+
+	session = (MsimSession *)gc->proto_data;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), 0);
+
+	switch (state) {
+		case PURPLE_TYPING: 
+			typing_str = "%typing%"; 
+			break;
+
+		case PURPLE_TYPED:
+		case PURPLE_NOT_TYPING:
+		default:
+			typing_str = "%stoptyping%";
+			break;
+	}
+
+	purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
+	msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
+	return 0;
+}
+
+
+
+/** Callback for msim_get_info(), for when user info is received. */
+static void 
+msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg, 
+		gpointer data)
+{
+	MsimMessage *msg;
+	gchar *username;
+	PurpleNotifyUserInfo *user_info;
+	MsimUser *user;
+	gboolean temporary_user;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	/* Get user{name,id} from msim_get_info, passed as an MsimMessage for 
+	   orthogonality. */
+	msg = (MsimMessage *)data;
+	g_return_if_fail(msg != NULL);
+
+	username = msim_msg_get_string(msg, "user");
+	if (!username) {
+		purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n");
+		return;
+	}
+
+	msim_msg_free(msg);
+	purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username);
+
+	user = msim_find_user(session, username);
+
+	if (!user) {
+		/* User isn't on blist, create a temporary user to store info. */
+		temporary_user = TRUE;
+		user = g_new0(MsimUser, 1);
+	} else {
+		temporary_user = FALSE;
+	}
+
+	/* Update user structure with new information */
+	msim_store_user_info(session, user_info_msg, user);
+
+	user_info = purple_notify_user_info_new();
+
+	/* Append data from MsimUser to PurpleNotifyUserInfo for display, full */
+	msim_append_user_info(session, user_info, user, TRUE);
+
+	purple_notify_userinfo(session->gc, username, user_info, NULL, NULL);
+	purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username);
+
+	purple_notify_user_info_destroy(user_info);
+	/* TODO: do not free username, since it will be used by user_info? */
+
+	if (temporary_user) {
+		g_free(user->client_info);
+		g_free(user->gender);
+		g_free(user->location);
+		g_free(user->headline);
+		g_free(user->display_name);
+		g_free(user->username);
+		g_free(user->band_name);
+		g_free(user->song_name);
+		g_free(user->image_url);
+		g_free(user);
+	}
+
+}
+
+/** Retrieve a user's profile. 
+ * @param username Username, user ID, or email address to lookup.
+ */
+void 
+msim_get_info(PurpleConnection *gc, const gchar *username)
+{
+	MsimSession *session;
+	MsimUser *user;
+	guint uid;
+	gchar *user_to_lookup;
+	MsimMessage *user_msg;
+
+	g_return_if_fail(gc != NULL);
+	g_return_if_fail(username != NULL);
+
+	session = (MsimSession *)gc->proto_data;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	/* Obtain uid of buddy. */
+	user = msim_find_user(session, username);
+
+	/* If is on buddy list, lookup by uid since it is faster. */
+	if (user && (uid = purple_blist_node_get_int(&user->buddy->node, "UserID"))) {
+		user_to_lookup = g_strdup_printf("%d", uid);
+	} else {
+		/* Looking up buddy not on blist. Lookup by whatever user entered. */
+		user_to_lookup = g_strdup(username);
+	}
+
+	/* Pass the username to msim_get_info_cb(), because since we lookup
+	 * by userid, the userinfo message will only contain the uid (not 
+	 * the username) but it would be useful to display the username too.
+	 */
+	user_msg = msim_msg_new(
+			"user", MSIM_TYPE_STRING, g_strdup(username),
+			NULL);
+	purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username);
+
+	msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg);
+
+	g_free(user_to_lookup); 
+}
+
+/** Set your status - callback for when user manually sets it.  */
+void
+msim_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+	PurpleStatusType *type;
+	MsimSession *session;
+	guint status_code;
+	const gchar *statstring;
+
+	session = (MsimSession *)account->gc->proto_data;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	type = purple_status_get_type(status);
+
+	switch (purple_status_type_get_primitive(type)) {
+		case PURPLE_STATUS_AVAILABLE:
+			purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE,
+					MSIM_STATUS_CODE_ONLINE);
+			status_code = MSIM_STATUS_CODE_ONLINE;
+			break;
+
+		case PURPLE_STATUS_INVISIBLE:
+			purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE,
+					MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN);
+			status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN;
+			break;
+
+		case PURPLE_STATUS_AWAY:
+			purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY,
+					MSIM_STATUS_CODE_AWAY);
+			status_code = MSIM_STATUS_CODE_AWAY;
+			break;
+
+		default:
+			purple_debug_info("msim", "msim_set_status: unknown "
+					"status interpreting as online");
+			status_code = MSIM_STATUS_CODE_ONLINE;
+			break;
+	}
+
+	statstring = purple_status_get_attr_string(status, "message");
+
+	if (!statstring) {
+		statstring = "";
+	}
+
+	/* Status strings are plain text. */
+	statstring = purple_markup_strip_html(statstring);
+
+	msim_set_status_code(session, status_code, g_strdup(statstring));
+}
+
+/** Go idle. */
+void
+msim_set_idle(PurpleConnection *gc, int time)
+{
+	MsimSession *session;
+
+	g_return_if_fail(gc != NULL);
+
+	session = (MsimSession *)gc->proto_data;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+
+	if (time == 0) {
+		/* Going back from idle. In msim, idle is mutually exclusive 
+		 * from the other states (you can only be away or idle, but not
+		 * both, for example), so by going non-idle I go online.
+		 */
+		/* TODO: find out how to keep old status string? */
+		msim_set_status_code(session, MSIM_STATUS_CODE_ONLINE, g_strdup(""));
+	} else {
+		/* msim doesn't support idle time, so just go idle */
+		msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, g_strdup(""));
+	}
+}
+
+/** Set status using an MSIM_STATUS_CODE_* value.
+ * @param status_code An MSIM_STATUS_CODE_* value.
+ * @param statstring Status string, must be a dynamic string (will be freed by msim_send).
+ */
+static void 
+msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring)
+{
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+	g_return_if_fail(statstring != NULL);
+
+	purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n",
+			status_code, statstring);
+
+	if (!msim_send(session,
+			"status", MSIM_TYPE_INTEGER, status_code,
+			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
+			"statstring", MSIM_TYPE_STRING, statstring, 
+			"locstring", MSIM_TYPE_STRING, g_strdup(""),
+			NULL))
+	{
+		purple_debug_info("msim", "msim_set_status: failed to set status\n");
+	}
+
+}
+
+/** After a uid is resolved to username, tag it with the username and submit for processing. 
+ * 
+ * @param session
+ * @param userinfo Response messsage to resolving request.
+ * @param data MsimMessage *, the message to attach information to. 
+ */
+static void 
+msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo, 
+		gpointer data)
+{
+	gchar *username;
+	MsimMessage *msg, *body;
+
+	g_return_if_fail(MSIM_SESSION_VALID(session));
+	g_return_if_fail(userinfo != NULL);
+
+	body = msim_msg_get_dictionary(userinfo, "body");
+	g_return_if_fail(body != NULL);
+
+	username = msim_msg_get_string(body, "UserName");
+	g_return_if_fail(username != NULL);
+	/* Note: username will be owned by 'msg' below. */
+
+	msg = (MsimMessage *)data;
+	g_return_if_fail(msg != NULL);
+
+	/* TODO: more elegant solution than below. attach whole message? */
+	/* Special elements name beginning with '_', we'll use internally within the
+	 * program (did not come directly from the wire). */
+	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username);
+  
+	/* TODO: attach more useful information, like ImageURL */
+
+	msim_process(session, msg);
+
+	/* TODO: Free copy cloned from  msim_preprocess_incoming(). */
+	//XXX msim_msg_free(msg);
+	msim_msg_free(body);
+}
+
+/* Lookup a username by userid, from buddy list. 
+ *
+ * @param wanted_uid
+ *
+ * @return Username of wanted_uid, if on blist, or NULL. Static string. 
+ *
+ */
+static const gchar *
+msim_uid2username_from_blist(MsimSession *session, guint wanted_uid)
+{
+	GSList *buddies, *cur;
+	gchar *ret;
+
+	buddies = purple_find_buddies(session->account, NULL); 
+
+	if (!buddies)
+	{
+		purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n");
+		return NULL;
+	}
+
+	ret = NULL;
+
+	for (cur = buddies; cur != NULL; cur = g_slist_next(cur))
+	{
+		PurpleBuddy *buddy;
+		guint uid;
+		const gchar *name;
+
+		/* See finch/gnthistory.c */
+		buddy = cur->data;
+
+		uid = purple_blist_node_get_int(&buddy->node, "UserID");
+		name = purple_buddy_get_name(buddy);
+
+		if (uid == wanted_uid)
+		{
+			ret = g_strdup(name);
+            break;
+		}
+	}
+
+	g_slist_free(buddies);
+	return ret;
+}
+
+/** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process.
+ *
+ * @param session
+ * @param msg MsimMessage *, freed by caller.
+ */
+static gboolean 
+msim_preprocess_incoming(MsimSession *session, MsimMessage *msg)
+{
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+	g_return_val_if_fail(msg != NULL, FALSE);
+
+	if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) {
+		guint uid;
+		const gchar *username;
+
+		/* 'f' = userid message is from, in buddy messages */
+		uid = msim_msg_get_integer(msg, "f");
+
+		username = msim_uid2username_from_blist(session, uid); 
+
+		if (username) {
+			/* Know username already, use it. */
+			purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n",
+					username);
+			msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
+			return msim_process(session, msg);
+
+		} else {
+			gchar *from;
+
+			/* Send lookup request. */
+			/* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */
+			purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n");
+			from = msim_msg_get_string(msg, "f");
+			msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg)); 
+			g_free(from);
+
+			/* indeterminate */
+			return TRUE;
+		}
+	} else {
+		/* Nothing to resolve - send directly to processing. */
+		return msim_process(session, msg);
+	}
+}
+
+#ifdef MSIM_USE_KEEPALIVE
+/** Check if the connection is still alive, based on last communication. */
+static gboolean
+msim_check_alive(gpointer data)
+{
+	MsimSession *session;
+	time_t delta;
+	gchar *errmsg;
+
+	session = (MsimSession *)data;
+
+	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
+
+	delta = time(NULL) - session->last_comm;
+	//purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
+	if (delta >= MSIM_KEEPALIVE_INTERVAL) {
+		errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), (int)delta);
+
+		purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",