# HG changeset patch # User Andreas Monitzer # Date 1189031534 0 # Node ID 1d2002a5735e0e43c80db8587e505c8f22d0f67a # Parent e21002d106ab69a06c838a82771cb582328456eb# Parent b0733d5d76216b57ff28e2000220740ef0cb4f17 propagate from branch 'im.pidgin.pidgin' (head 996cf0c57149ba6e1c714ebb1f11d5d4bac8fb68) to branch 'im.pidgin.soc.2007.xmpp' (head cdf63b6603891b8cd3e7f629ef5a9a927a153550) diff -r b0733d5d7621 -r 1d2002a5735e .mtn-ignore --- a/.mtn-ignore Tue Aug 28 19:03:07 2007 +0000 +++ b/.mtn-ignore Wed Sep 05 22:32:14 2007 +0000 @@ -34,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$ diff -r b0733d5d7621 -r 1d2002a5735e COPYRIGHT --- a/COPYRIGHT Tue Aug 28 19:03:07 2007 +0000 +++ b/COPYRIGHT Wed Sep 05 22:32:14 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 @@ -305,6 +307,7 @@ Bob Rossi Jason Roth Jean-Francois Roy +Peter Ruibal Sam S. Pradyumna Sampath Arvind Samptur @@ -335,6 +338,7 @@ Michael Shkutkov Ettore Simone John Silvestri +Ankit Singla Craig Slusher Alex Smith Brad Smith @@ -345,6 +349,7 @@ Sony Computer Entertainment America, Inc. Andy Spencer Mark Spencer +Peter Speybrouck Lex Spoon Chris Stafford Kevin Stange @@ -371,6 +376,7 @@ Warren Togami Stu Tomlinson Bill Tompkins +Gal Topper Chris Toshok Ken Tossell Tom Tromey diff -r b0733d5d7621 -r 1d2002a5735e ChangeLog --- a/ChangeLog Tue Aug 28 19:03:07 2007 +0000 +++ b/ChangeLog Wed Sep 05 22:32:14 2007 +0000 @@ -7,6 +7,10 @@ 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) diff -r b0733d5d7621 -r 1d2002a5735e ChangeLog.API --- a/ChangeLog.API Tue Aug 28 19:03:07 2007 +0000 +++ b/ChangeLog.API Wed Sep 05 22:32:14 2007 +0000 @@ -1,10 +1,33 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul 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. + + 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: diff -r b0733d5d7621 -r 1d2002a5735e ChangeLog.win32 --- a/ChangeLog.win32 Tue Aug 28 19:03:07 2007 +0000 +++ b/ChangeLog.win32 Wed Sep 05 22:32:14 2007 +0000 @@ -1,5 +1,9 @@ +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 + * No changes version 2.1.0 (7/28/2007): * Updated launcher application (pidgin.exe) to support portable mode diff -r b0733d5d7621 -r 1d2002a5735e Makefile.mingw --- a/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/Makefile.mingw Wed Sep 05 22:32:14 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 diff -r b0733d5d7621 -r 1d2002a5735e configure.ac --- a/configure.ac Tue Aug 28 19:03:07 2007 +0000 +++ b/configure.ac Wed Sep 05 22:32:14 2007 +0000 @@ -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 #include - ], [], [], [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) @@ -2208,6 +2240,7 @@ libpurple/version.h share/Makefile share/sounds/Makefile + share/ca-certs/Makefile finch/Makefile finch/libgnt/Makefile finch/libgnt/gnt.pc @@ -2224,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 diff -r b0733d5d7621 -r 1d2002a5735e doc/Makefile.am --- a/doc/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/doc/Makefile.am Wed Sep 05 22:32:14 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 \ diff -r b0733d5d7621 -r 1d2002a5735e doc/SIGNAL-HOWTO.dox --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/SIGNAL-HOWTO.dox Wed Sep 05 22:32:14 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. + + */ diff -r b0733d5d7621 -r 1d2002a5735e doc/certificate-signals.dox --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/certificate-signals.dox Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,31 @@ +/** @page certificate-signals Certificate Signals + + @signals + @signal certificate-stored + @signal certificate-deleted + @endsignals + +
+ + @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 diff -r b0733d5d7621 -r 1d2002a5735e doc/gtkconv-signals.dox --- a/doc/gtkconv-signals.dox Tue Aug 28 19:03:07 2007 +0000 +++ b/doc/gtkconv-signals.dox Wed Sep 05 22:32:14 2007 +0000 @@ -8,6 +8,8 @@ @signal displaying-chat-msg @signal displayed-chat-msg @signal conversation-switched + @signal conversation-hiding + @signal conversation-displayed @endsignals
@@ -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 diff -r b0733d5d7621 -r 1d2002a5735e finch/Makefile.am --- a/finch/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/Makefile.am Wed Sep 05 22:32:14 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 \ diff -r b0733d5d7621 -r 1d2002a5735e finch/finch.c --- a/finch/finch.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/finch.c Wed Sep 05 22:32:14 2007 +0000 @@ -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 */ diff -r b0733d5d7621 -r 1d2002a5735e finch/gntcertmgr.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntcertmgr.c Wed Sep 05 22:32:14 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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); +} + diff -r b0733d5d7621 -r 1d2002a5735e finch/gntcertmgr.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/gntcertmgr.h Wed Sep 05 22:32:14 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef _GNT_CERTMGR_H +#define _GNT_CERTMGR_H + +void finch_certmgr_show(void); + +#endif diff -r b0733d5d7621 -r 1d2002a5735e finch/gntconv.c --- a/finch/gntconv.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/gntconv.c Wed Sep 05 22:32:14 2007 +0000 @@ -613,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); @@ -1006,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; } diff -r b0733d5d7621 -r 1d2002a5735e finch/gntrequest.c --- a/finch/gntrequest.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/gntrequest.c Wed Sep 05 22:32:14 2007 +0000 @@ -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, void *user_data, size_t actioncount, va_list actions) diff -r b0733d5d7621 -r 1d2002a5735e finch/gntui.c --- a/finch/gntui.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/gntui.c Wed Sep 05 22:32:14 2007 +0000 @@ -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); diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/gntcheckbox.c diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/gntentry.c diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/gntlabel.c diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/gntmenu.c --- a/finch/libgnt/gntmenu.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/libgnt/gntmenu.c Wed Sep 05 22:32:14 2007 +0000 @@ -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) diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/pygnt/dbus-gnt --- a/finch/libgnt/pygnt/dbus-gnt Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/libgnt/pygnt/dbus-gnt Wed Sep 05 22:32:14 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() diff -r b0733d5d7621 -r 1d2002a5735e finch/libgnt/pygnt/gendef.sh --- a/finch/libgnt/pygnt/gendef.sh Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/libgnt/pygnt/gendef.sh Wed Sep 05 22:32:14 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 diff -r b0733d5d7621 -r 1d2002a5735e finch/plugins/gntclipboard.c --- a/finch/plugins/gntclipboard.c Tue Aug 28 19:03:07 2007 +0000 +++ b/finch/plugins/gntclipboard.c Wed Sep 05 22:32:14 2007 +0000 @@ -38,10 +38,12 @@ #include #include #include +#include #include #include +#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; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/Makefile.am --- a/libpurple/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -22,9 +22,9 @@ win32/giowin32.c \ win32/win32dep.h -# if USE_GCONFTOOL -# GCONF_DIR=gconf -# endif +if USE_GCONFTOOL +GCONF_DIR=gconf +endif pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = purple.pc @@ -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 \ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -33,6 +33,7 @@ accountopt.c \ blist.c \ buddyicon.c \ + certificate.c \ cipher.c \ cmds.c \ connection.c \ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/account.c --- a/libpurple/account.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/account.c Wed Sep 05 22:32:14 2007 +0000 @@ -1740,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; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/certificate.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,1880 @@ +/** + * @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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#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 */ +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) */ + + + + /* 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); +} + diff -r b0733d5d7621 -r 1d2002a5735e libpurple/certificate.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/certificate.h Wed Sep 05 22:32:14 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _PURPLE_CERTIFICATE_H +#define _PURPLE_CERTIFICATE_H + +#include + +#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 */ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/conversation.c --- a/libpurple/conversation.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/conversation.c Wed Sep 05 22:32:14 2007 +0000 @@ -39,6 +39,7 @@ static GList *ims = NULL; static GList *chats = NULL; static PurpleConversationUiOps *default_ops = NULL; +static GHashTable *histories = NULL; void purple_conversations_set_ui_ops(PurpleConversationUiOps *ops) @@ -111,17 +112,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 +203,51 @@ 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) +{ + GList *list; + 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; + + list = g_hash_table_lookup(histories, conv); + list = g_list_prepend(list, msg); + g_hash_table_insert(histories, conv, list); +} + +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 +519,8 @@ purple_conversation_close_logs(conv); + purple_conversation_clear_message_history(conv); + PURPLE_DBUS_UNREGISTER_POINTER(conv); g_free(conv); conv = NULL; @@ -805,9 +853,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 +869,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 +887,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 +935,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 +2066,42 @@ return menu; } +void purple_conversation_clear_message_history(PurpleConversation *conv) +{ + GList *list = g_hash_table_lookup(histories, conv); + message_history_free(list); + g_hash_table_remove(histories, conv); +} + +GList *purple_conversation_get_message_history(PurpleConversation *conv) +{ + return g_hash_table_lookup(histories, conv); +} + +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) @@ -2300,6 +2382,9 @@ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONVERSATION), purple_value_new(PURPLE_TYPE_BOXED, "GList **")); + + /* Initialize the history */ + histories = g_hash_table_new(g_direct_hash, g_direct_equal); } void @@ -2308,4 +2393,6 @@ while (conversations) purple_conversation_destroy((PurpleConversation*)conversations->data); purple_signals_unregister_by_instance(purple_conversations_get_handle()); + g_hash_table_destroy(histories); + histories = NULL; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/conversation.h --- a/libpurple/conversation.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/conversation.h Wed Sep 05 22:32:14 2007 +0000 @@ -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; /** @@ -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. @@ -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); + /*@}*/ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/core.c --- a/libpurple/core.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/core.c Wed Sep 05 22:32:14 2007 +0000 @@ -24,6 +24,7 @@ */ #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(); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/dbus-analyze-functions.py --- a/libpurple/dbus-analyze-functions.py Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/dbus-analyze-functions.py Wed Sep 05 22:32:14 2007 +0000 @@ -66,6 +66,7 @@ "purple_savedstatuses_get_all", "purple_status_type_get_attrs", "purple_presence_get_statuses", + "purple_conversation_get_message_history", ] pointer = "#pointer#" diff -r b0733d5d7621 -r 1d2002a5735e libpurple/example/nullclient.c --- a/libpurple/example/nullclient.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/example/nullclient.c Wed Sep 05 22:32:14 2007 +0000 @@ -197,7 +197,7 @@ purple_util_set_user_dir(CUSTOM_USER_DIRECTORY); /* We do not want any debugging for now to keep the noise to a minimum. */ - purple_debug_set_enabled(TRUE); + purple_debug_set_enabled(FALSE); /* Set the core-uiops, which is used to * - initialize the ui specific preferences. @@ -257,24 +257,6 @@ PURPLE_CALLBACK(signed_on), NULL); } - - - -void signedOn( PurpleConnection *gc, gpointer dummy ) { - - - if( gc ) { - - PurpleAccount* a = purple_connection_get_account( gc ); - - if( a ) { - - purple_presence_set_idle( purple_account_get_presence( a ), TRUE, time( NULL ) ); - } - } -} - - int main() { GList *iter; @@ -300,26 +282,30 @@ names = g_list_append(names, info->id); } } + printf("Select the protocol [0-%d]: ", i-1); + fgets(name, sizeof(name), stdin); + sscanf(name, "%d", &num); + prpl = g_list_nth_data(names, num); + + printf("Username: "); + fgets(name, sizeof(name), stdin); + name[strlen(name) - 1] = 0; /* strip the \n at the end */ /* Create the account */ - account = purple_account_new("msimprpl@xyzzy.cjb.net", "prpl-myspace" ); - purple_account_set_password(account, "4224jc" ); + account = purple_account_new(name, prpl); + + /* Get the password for the account */ + password = getpass("Password: "); + purple_account_set_password(account, password); /* It's necessary to enable the account first. */ purple_account_set_enabled(account, UI_ID, TRUE); -#if 0 - static int handle; - purple_signal_connect( purple_connections_get_handle(), - "signed-on", &handle, - PURPLE_CALLBACK( signedOn ), - NULL ); - /* Now, to connect the account(s), create a status and activate it. */ - purple_savedstatus_activate( purple_savedstatus_get_default() ); + status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE); + purple_savedstatus_activate(status); connect_to_signals_for_demonstration_purposes_only(); -#endif g_main_loop_run(loop); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/internal.h --- a/libpurple/internal.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/internal.h Wed Sep 05 22:32:14 2007 +0000 @@ -29,6 +29,10 @@ # include #endif +/* for SIOCGIFCONF in SKYOS */ +#ifdef SKYOS +#include +#endif /* * If we're using NLS, make sure gettext works. If not, then define * dummy macros in place of the normal gettext macros. diff -r b0733d5d7621 -r 1d2002a5735e libpurple/network.c --- a/libpurple/network.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/network.c Wed Sep 05 22:32:14 2007 +0000 @@ -351,6 +351,7 @@ 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), diff -r b0733d5d7621 -r 1d2002a5735e libpurple/plugins/offlinemsg.c --- a/libpurple/plugins/offlinemsg.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/plugins/offlinemsg.c Wed Sep 05 22:32:14 2007 +0000 @@ -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; @@ -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; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/plugins/ssl/Makefile.mingw --- a/libpurple/plugins/ssl/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/plugins/ssl/Makefile.mingw Wed Sep 05 22:32:14 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) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/plugins/ssl/ssl-gnutls.c --- a/libpurple/plugins/ssl/ssl-gnutls.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-gnutls.c Wed Sep 05 22:32:14 2007 +0000 @@ -21,15 +21,18 @@ */ #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 +#include 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; iverifier) { + 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; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/plugins/ssl/ssl-nss.c --- a/libpurple/plugins/ssl/ssl-nss.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-nss.c Wed Sep 05 22:32:14 2007 +0000 @@ -21,6 +21,7 @@ */ #include "internal.h" #include "debug.h" +#include "certificate.h" #include "plugin.h" #include "sslconn.h" #include "version.h" @@ -360,6 +361,294 @@ return ret; } +static GList * +ssl_nss_peer_certs(PurpleSslConnection *gsc) +{ + PurpleSslNssData *nss_data = PURPLE_SSL_NSS_DATA(gsc); + GList *chain = NULL; + CERTCertificate *cert; + 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); +} + +/** 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; +} + +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 +657,11 @@ ssl_nss_close, ssl_nss_read, ssl_nss_write, + ssl_nss_peer_certs, /* padding */ NULL, NULL, - NULL, NULL }; @@ -390,6 +679,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 +695,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; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/plugins/tcl/tcl_cmds.c --- a/libpurple/plugins/tcl/tcl_cmds.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_cmds.c Wed Sep 05 22:32:14 2007 +0000 @@ -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"); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/prefs.c --- a/libpurple/prefs.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/prefs.c Wed Sep 05 22:32:14 2007 +0000 @@ -1446,6 +1446,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 diff -r b0733d5d7621 -r 1d2002a5735e libpurple/prefs.h --- a/libpurple/prefs.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/prefs.h Wed Sep 05 22:32:14 2007 +0000 @@ -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) */ /**************************************************************************/ /*@{*/ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/Makefile.mingw --- a/libpurple/protocols/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/Makefile.mingw Wed Sep 05 22:32:14 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 myspace +SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc simple yahoo bonjour myspace .PHONY: all install clean diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Wed Sep 05 22:32:14 2007 +0000 @@ -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')) + ; +#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 */ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/buddy.c --- a/libpurple/protocols/bonjour/buddy.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Wed Sep 05 22:32:14 2007 +0000 @@ -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); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/buddy.h --- a/libpurple/protocols/bonjour/buddy.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Wed Sep 05 22:32:14 2007 +0000 @@ -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 diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/issues.txt --- a/libpurple/protocols/bonjour/issues.txt Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/issues.txt Wed Sep 05 22:32:14 2007 +0000 @@ -2,6 +2,5 @@ ============= Known issues =============== ========================================== -* Status changes don't work * File transfers * Typing notifications diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Wed Sep 05 22:32:14 2007 +0000 @@ -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) { diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/mdns_avahi.c --- a/libpurple/protocols/bonjour/mdns_avahi.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Wed Sep 05 22:32:14 2007 +0000 @@ -33,7 +33,7 @@ #include #include -/* 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); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/mdns_howl.c --- a/libpurple/protocols/bonjour/mdns_howl.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Wed Sep 05 22:32:14 2007 +0000 @@ -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"); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/bonjour/mdns_win32.c --- a/libpurple/protocols/bonjour/mdns_win32.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Wed Sep 05 22:32:14 2007 +0000 @@ -79,23 +79,23 @@ 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); @@ -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"); 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); } } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Wed Sep 05 22:32:14 2007 +0000 @@ -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) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Sep 05 22:32:14 2007 +0000 @@ -510,21 +510,14 @@ 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_with_host(js->gc->account, js->fd, + 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); } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Sep 05 22:32:14 2007 +0000 @@ -143,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 */ @@ -157,7 +159,6 @@ int sasl_state; int sasl_maxbuf; GString *sasl_mechs; - char *serverFQDN; gboolean unregistration; PurpleAccountUnregistrationCb unregistration_cb; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/msn/slp.c Wed Sep 05 22:32:14 2007 +0000 @@ -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) { diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.c Wed Sep 05 22:32:14 2007 +0000 @@ -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); } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/myspace/CHANGES --- a/libpurple/protocols/myspace/CHANGES Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/myspace/CHANGES Wed Sep 05 22:32:14 2007 +0000 @@ -1,3 +1,13 @@ +2007-08-28 Jeff Connelly - 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 - 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 @@ -6,6 +16,7 @@ * 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 - 0.15 * Incomplete implementation of adding friends from myspace.com. diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/myspace/markup.c --- a/libpurple/protocols/myspace/markup.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/myspace/markup.c Wed Sep 05 22:32:14 2007 +0000 @@ -669,6 +669,8 @@ /** 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) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.c Wed Sep 05 22:32:14 2007 +0000 @@ -683,6 +683,7 @@ 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); @@ -696,8 +697,12 @@ msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); g_free(msg_msim_markup); - serv_got_im(session->gc, username, msg_purple_markup, - PURPLE_MESSAGE_RECV, time(NULL)); + 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); @@ -1061,9 +1066,12 @@ statstring = purple_status_get_attr_string(status, "message"); if (!statstring) { - statstring = g_strdup(""); + statstring = ""; } + /* Status strings are plain text. */ + statstring = purple_markup_strip_html(statstring); + msim_set_status_code(session, status_code, g_strdup(statstring)); } @@ -1402,6 +1410,91 @@ return TRUE; } +#ifdef MSIM_CHECK_NEWER_VERSION +/** Callback for when a currentversion.txt has been downloaded. */ +static void +msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data, + gpointer user_data, + const gchar *url_text, + gsize len, + const gchar *error_message) +{ + GKeyFile *keyfile; + GError *error; + GString *data; + gchar *newest_filever; + + if (!url_text) { + purple_debug_info("msim_check_newer_version_cb", + "got error: %s\n", error_message); + return; + } + + purple_debug_info("msim_check_newer_version_cb", + "url_text=%s\n", url_text ? url_text : "(NULL)"); + + /* Prepend [group] so that GKeyFile can parse it (requires a group). */ + data = g_string_new(url_text); + purple_debug_info("msim", "data=%s\n", data->str + ? data->str : "(NULL)"); + data = g_string_prepend(data, "[group]\n"); + + purple_debug_info("msim", "data=%s\n", data->str + ? data->str : "(NULL)"); + + /* url_text is variable=data\n... */ + + /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */ + /* New (english) version can be downloaded from SETUPURL+SETUPFILE */ + + error = NULL; + keyfile = g_key_file_new(); + + /* Default list seperator is ;, but currentversion.txt doesn't have + * these, so set to an unused character to avoid parsing problems. */ + g_key_file_set_list_separator(keyfile, '\0'); + + g_key_file_load_from_data(keyfile, data->str, data->len, + G_KEY_FILE_NONE, &error); + g_string_free(data, TRUE); + + if (error != NULL) { + purple_debug_info("msim_check_newer_version_cb", + "couldn't parse, error: %d %d %s\n", + error->domain, error->code, error->message); + g_error_free(error); + return; + } + + gchar **ks; + guint n; + ks = g_key_file_get_keys(keyfile, "group", &n, NULL); + purple_debug_info("msim", "n=%d\n", n); + guint i; + for (i = 0; ks[i] != NULL; ++i) + { + purple_debug_info("msim", "%d=%s\n", i, ks[i]); + } + + newest_filever = g_key_file_get_string(keyfile, "group", + "FILEVER", &error); + + purple_debug_info("msim_check_newer_version_cb", + "newest filever: %s\n", newest_filever ? + newest_filever : "(NULL)"); + if (error != NULL) { + purple_debug_info("msim_check_newer_version_cb", + "error: %d %d %s\n", + error->domain, error->code, error->message); + g_error_free(error); + } + + g_key_file_free(keyfile); + + exit(0); +} +#endif + /** Called when the session key arrives. */ static gboolean msim_we_are_logged_on(MsimSession *session, MsimMessage *msg) @@ -1436,7 +1529,7 @@ /* If a local alias wasn't set, set it to user's username. */ if (!session->account->alias || !strlen(session->account->alias)) - session->account->alias = session->username; + purple_account_set_alias(session->account, session->username); /* The session is now set up, ready to be connected. This emits the * signedOn signal, so clients can now do anything with msimprpl, and @@ -1682,15 +1775,23 @@ purple_debug_info("msim", "msim_error (sesskey=%d): %s\n", session->sesskey, full_errmsg); - purple_notify_error(session->account, g_strdup(_("MySpaceIM Error")), - full_errmsg, NULL); - /* Destroy session if fatal. */ if (msim_msg_get(msg, "fatal")) { purple_debug_info("msim", "fatal error, closing\n"); + if (err == 260) { + /* Incorrect password */ + session->gc->wants_to_die = TRUE; + if (!purple_account_get_remember_password(session->account)) + purple_account_set_password(session->account, NULL); + } purple_connection_error(session->gc, full_errmsg); + } else { + purple_notify_error(session->account, g_strdup(_("MySpaceIM Error")), + full_errmsg, NULL); } + g_free(full_errmsg); + return TRUE; } @@ -1708,7 +1809,7 @@ PurpleBuddyList *blist; MsimUser *user; GList *list; - gchar *status_headline; + gchar *status_headline, *status_headline_escaped; gint status_code, purple_status_code; gchar *username; @@ -1763,8 +1864,16 @@ purple_debug_info("msim", "msim_status: found buddy %s\n", username); } + /* The status headline is plaintext, but libpurple treats it as HTML, + * so escape any HTML characters to their entity equivalents. */ + status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline)); + g_free(status_headline); + + if (user->headline) + g_free(user->headline); + /* don't copy; let the MsimUser own the headline, memory-wise */ - user->headline = status_headline; + user->headline = status_headline_escaped; /* Set user status */ switch (status_code) { @@ -1828,7 +1937,7 @@ { MsimSession *session; MsimMessage *msg; - /* MsimMessage *msg_persist; */ + MsimMessage *msg_persist; MsimMessage *body; session = (MsimSession *)gc->proto_data; @@ -1863,12 +1972,12 @@ /* TODO: Update blocklist. */ -#if 0 msg_persist = msim_msg_new( "persist", MSIM_TYPE_INTEGER, 1, "sesskey", MSIM_TYPE_INTEGER, session->sesskey, "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT, "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN, + "uid", MSIM_TYPE_INTEGER, session->userid, "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID, /* TODO: Use msim_new_reply_callback to get rid. */ "rid", MSIM_TYPE_INTEGER, session->next_rid++, @@ -1882,7 +1991,6 @@ return; } msim_msg_free(msg_persist); -#endif } @@ -2613,7 +2721,7 @@ switch (GPOINTER_TO_UINT(user_data)) { case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS: - msg = g_strdup_printf(_("%d buddies were added or updated"), buddy_count); + msg = g_strdup_printf(_("%d buddies were added or updated from the server (including buddies already on the server-side list)"), buddy_count); purple_notify_info(session->account, _("Add contacts from server"), msg, NULL); g_free(msg); break; @@ -3138,6 +3246,16 @@ PurpleAccountOption *option; static gboolean initialized = FALSE; +#ifdef MSIM_CHECK_NEWER_VERSION + /* PROBLEM: MySpace's servers always return Content-Location, and + * libpurple redirects to it, infinitely, even though it is the same + * location we requested! */ + purple_util_fetch_url("http://im.myspace.com/nsis/currentversion.txt", + FALSE, /* not full URL */ + "MSIMAutoUpdateAgent", /* user agent */ + TRUE, /* use HTTP/1.1 */ + msim_check_newer_version_cb, NULL); +#endif /* TODO: default to automatically try different ports. Make the user be * able to set the first port to try (like LastConnectedPort in Windows client). */ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/myspace/myspace.h --- a/libpurple/protocols/myspace/myspace.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.h Wed Sep 05 22:32:14 2007 +0000 @@ -91,12 +91,16 @@ /* Build version of MySpaceIM to report to servers (1.0.xxx.0) */ #define MSIM_CLIENT_VERSION 697 +/* Check for a newer official MySpaceIM client on startup? + * (Mostly useful for developers) */ +/*#define MSIM_CHECK_NEWER_VERSION*/ + /* Language codes from http://www.microsoft.com/globaldev/reference/oslocversion.mspx */ #define MSIM_LANGUAGE_ID_ENGLISH 1033 #define MSIM_LANGUAGE_NAME_ENGLISH "ENGLISH" /* msimprpl version string of this plugin */ -#define MSIM_PRPL_VERSION_STRING "0.16" +#define MSIM_PRPL_VERSION_STRING "0.18" /* Default server */ #define MSIM_SERVER "im.myspace.akadns.net" @@ -186,10 +190,10 @@ /** A type of "attention" message (zap, nudge, buzz, etc. depending on the * protocol) that can be sent and received. */ struct _MsimAttentionType { - PurpleStoredImage *icon; - const gchar *description; /**< Shown before sending. */ + const gchar *name; /**< Shown before sending. */ const gchar *incoming_description; /**< Shown when sent. */ const gchar *outgoing_description; /**< Shown when received. */ + const gchar *icon_name; }; #endif diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/myspace/release.sh --- a/libpurple/protocols/myspace/release.sh Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/myspace/release.sh Wed Sep 05 22:32:14 2007 +0000 @@ -4,7 +4,7 @@ # Package a new msimprpl for release. Must be run with bash. -VERSION=0.16 +VERSION=0.18 make # Include 'myspace' directory in archive, so it can easily be unextracted # into ~/pidgin/libpurple/protocols at the correct location. diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/family_auth.c --- a/libpurple/protocols/oscar/family_auth.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/family_auth.c Wed Sep 05 22:32:14 2007 +0000 @@ -211,7 +211,7 @@ #ifdef USE_XOR_FOR_ICQ /* If we're signing on an ICQ account then use the older, XOR login method */ - if (aim_sn_is_icq(sn)) + if (aim_snvalid_icq(sn)) return goddamnicq2(od, conn, sn, password, ci); #endif @@ -224,7 +224,7 @@ /* Truncate ICQ and AOL passwords, if necessary */ password_len = strlen(password); - if (aim_sn_is_icq(sn) && (password_len > MAXICQPASSLEN)) + if (aim_snvalid_icq(sn) && (password_len > MAXICQPASSLEN)) password_len = MAXICQPASSLEN; else if (truncate_pass && password_len > 8) password_len = 8; @@ -477,7 +477,7 @@ return -EINVAL; #ifdef USE_XOR_FOR_ICQ - if (aim_sn_is_icq(sn)) + if (aim_snvalid_icq(sn)) return goddamnicq(od, conn, sn); #endif diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/family_chat.c diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/family_feedbag.c --- a/libpurple/protocols/oscar/family_feedbag.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/family_feedbag.c Wed Sep 05 22:32:14 2007 +0000 @@ -505,13 +505,13 @@ * buddy ID#s, which makes things more efficient. I think. */ - /* Additions */ + /* Deletions */ if (!od->ssi.pending) { - for (cur1=od->ssi.local; cur1 && (n < 15); cur1=cur1->next) { - if (!aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid)) { + for (cur1=od->ssi.official; cur1 && (n < 15); cur1=cur1->next) { + if (!aim_ssi_itemlist_find(od->ssi.local, cur1->gid, cur1->bid)) { n++; new = (struct aim_ssi_tmp *)g_malloc(sizeof(struct aim_ssi_tmp)); - new->action = SNAC_SUBTYPE_FEEDBAG_ADD; + new->action = SNAC_SUBTYPE_FEEDBAG_DEL; new->ack = 0xffff; new->name = NULL; new->item = cur1; @@ -525,13 +525,13 @@ } } - /* Deletions */ + /* Additions */ if (!od->ssi.pending) { - for (cur1=od->ssi.official; cur1 && (n < 15); cur1=cur1->next) { - if (!aim_ssi_itemlist_find(od->ssi.local, cur1->gid, cur1->bid)) { + for (cur1=od->ssi.local; cur1 && (n < 15); cur1=cur1->next) { + if (!aim_ssi_itemlist_find(od->ssi.official, cur1->gid, cur1->bid)) { n++; new = (struct aim_ssi_tmp *)g_malloc(sizeof(struct aim_ssi_tmp)); - new->action = SNAC_SUBTYPE_FEEDBAG_DEL; + new->action = SNAC_SUBTYPE_FEEDBAG_ADD; new->ack = 0xffff; new->name = NULL; new->item = cur1; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/family_icbm.c --- a/libpurple/protocols/oscar/family_icbm.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/family_icbm.c Wed Sep 05 22:32:14 2007 +0000 @@ -2101,7 +2101,11 @@ args.uin = byte_stream_getle32(&meat); args.type = byte_stream_getle8(&meat); args.flags = byte_stream_getle8(&meat); - args.msglen = byte_stream_getle16(&meat); + if (args.type == 0x1a) + /* There seems to be a problem with the length in SMS msgs from server, this fixed it */ + args.msglen = block->length - 6; + else + args.msglen = byte_stream_getle16(&meat); args.msg = (gchar *)byte_stream_getraw(&meat, args.msglen); if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/family_icq.c --- a/libpurple/protocols/oscar/family_icq.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/family_icq.c Wed Sep 05 22:32:14 2007 +0000 @@ -340,7 +340,6 @@ } #endif -#if 0 /* * Send an SMS message. This is the non-US way. The US-way is to IM * their cell phone number (+19195551234). @@ -369,6 +368,7 @@ const char *timestr; time_t t; struct tm *tm; + gchar *stripped; if (!od || !(conn = flap_connection_findbygroup(od, 0x0015))) return -EINVAL; @@ -380,22 +380,24 @@ tm = gmtime(&t); timestr = purple_utf8_strftime("%a, %d %b %Y %T %Z", tm); + stripped = purple_markup_strip_html(msg); + /* The length of xml included the null terminating character */ - xmllen = 225 + strlen(name) + strlen(msg) + strlen(od->sn) + strlen(alias) + strlen(timestr) + 1; + xmllen = 209 + strlen(name) + strlen(stripped) + strlen(od->sn) + strlen(alias) + strlen(timestr) + 1; xml = g_new(char, xmllen); - snprintf(xml, xmllen, "\n" - "\t%s\n" - "\t%s\n" - "\t1252\n" - "\t%s\n" - "\t%s\n" - "\tYes\n" - "\t\n" - "\n", - name, msg, od->sn, alias, timestr); + snprintf(xml, xmllen, "" + "%s" + "%s" + "1252" + "%s" + "%s" + "Yes" + "" + "", + name, stripped, od->sn, alias, timestr); - bslen = 37 + xmllen; + bslen = 36 + xmllen; frame = flap_frame_new(od, 0x02, 10 + 4 + bslen); @@ -412,7 +414,7 @@ byte_stream_putle16(&frame->data, snacid); /* eh. */ /* From libicq200-0.3.2/src/SNAC-SRV.cpp */ - byte_stream_putle16(&frame->data, 0x8214); + byte_stream_putle16(&frame->data, 0x1482); byte_stream_put16(&frame->data, 0x0001); byte_stream_put16(&frame->data, 0x0016); byte_stream_put32(&frame->data, 0x00000000); @@ -423,14 +425,15 @@ byte_stream_put16(&frame->data, 0x0000); byte_stream_put16(&frame->data, xmllen); byte_stream_putstr(&frame->data, xml); + byte_stream_put8(&frame->data, 0x00); flap_connection_send(conn, frame); g_free(xml); + g_free(stripped); return 0; } -#endif static void aim_icq_freeinfo(struct aim_icq_info *info) { int i; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Wed Sep 05 22:32:14 2007 +0000 @@ -358,7 +358,7 @@ const char *charset = NULL; char *ret = NULL; - if(aim_sn_is_icq(purple_account_get_username(account))) + if(aim_snvalid_icq(purple_account_get_username(account))) charset = purple_account_get_string(account, "encoding", NULL); if(charset && *charset) @@ -424,7 +424,7 @@ charsetstr1 = "UCS-2BE"; charsetstr2 = "UTF-8"; } else if (charset == AIM_CHARSET_CUSTOM) { - if ((sourcesn != NULL) && aim_sn_is_icq(sourcesn)) + if ((sourcesn != NULL) && aim_snvalid_icq(sourcesn)) charsetstr1 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); else charsetstr1 = "ISO-8859-1"; @@ -495,7 +495,7 @@ * capability, and they are online, then attempt to send * as UCS-2BE. */ - if ((destsn != NULL) && aim_sn_is_icq(destsn)) + if ((destsn != NULL) && aim_snvalid_icq(destsn)) userinfo = aim_locate_finduserinfo(od, destsn); if ((userinfo != NULL) && (userinfo->capabilities & OSCAR_CAPABILITY_UNICODE)) @@ -520,7 +520,7 @@ * ICQ then attempt to send as the user specified character encoding. */ charsetstr = "ISO-8859-1"; - if ((destsn != NULL) && aim_sn_is_icq(destsn)) + if ((destsn != NULL) && aim_snvalid_icq(destsn)) charsetstr = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); /* @@ -815,7 +815,7 @@ if (b != NULL) { if (purple_presence_is_online(presence)) { - if (aim_sn_is_icq(b->name)) { + if (aim_snvalid_icq(b->name)) { PurpleStatus *status = purple_presence_get_active_status(presence); oscar_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status)); } @@ -1019,7 +1019,7 @@ PURPLE_INPUT_READ, flap_connection_recv_cb, conn); if (conn->cookie == NULL) { - if (!aim_sn_is_icq(purple_account_get_username(account))) + if (!aim_snvalid_icq(purple_account_get_username(account))) /* * We don't send this when authenticating an ICQ account * because for some reason ICQ is still using the @@ -1261,7 +1261,7 @@ g_free(buf); } - if (aim_sn_is_icq((purple_account_get_username(account)))) { + if (aim_snvalid_icq((purple_account_get_username(account)))) { od->icq = TRUE; } else { gc->flags |= PURPLE_CONNECTION_HTML; @@ -1752,7 +1752,7 @@ } } - if (aim_sn_is_icq(info->sn)) { + if (aim_snvalid_icq(info->sn)) { if (type & AIM_ICQ_STATE_CHAT) status_id = OSCAR_STATUS_ID_FREE4CHAT; else if (type & AIM_ICQ_STATE_DND) @@ -1987,7 +1987,7 @@ * Note: There *may* be some clients which send messages as HTML formatted - * they need to be special-cased somehow. */ - if (aim_sn_is_icq(purple_account_get_username(account)) && aim_sn_is_icq(userinfo->sn)) { + if (aim_snvalid_icq(purple_account_get_username(account)) && aim_snvalid_icq(userinfo->sn)) { /* being recevied by ICQ from ICQ - escape HTML so it is displayed as sent */ gchar *tmp2 = g_markup_escape_text(tmp, -1); g_free(tmp); @@ -2486,8 +2486,48 @@ } } break; - case 0x1a: { /* Someone has sent you a greeting card or requested buddies? */ - /* This is boring and silly. */ + case 0x1a: { /* Handle SMS or someone has sent you a greeting card or requested buddies? */ + ByteStream qbs; + int smstype, taglen, smslen; + char *tagstr = NULL, *smsmsg = NULL; + xmlnode *xmlroot = NULL, *xmltmp = NULL; + gchar *uin = NULL, *message = NULL; + + /* From libicq2000-0.3.2/src/ICQ.cpp */ + byte_stream_init(&qbs, (guint8 *)args->msg, args->msglen); + byte_stream_advance(&qbs, 21); + smstype = byte_stream_getle16(&qbs); + taglen = byte_stream_getle32(&qbs); + tagstr = byte_stream_getstr(&qbs, taglen); + byte_stream_advance(&qbs, 3); + byte_stream_advance(&qbs, 4); + smslen = byte_stream_getle32(&qbs); + smsmsg = byte_stream_getstr(&qbs, smslen); + + /* Check if this is an SMS being sent from server */ + if ((smstype == 0) && (!strcmp(tagstr, "ICQSMS")) && (smsmsg != NULL)) + { + xmlroot = xmlnode_from_str(smsmsg, -1); + if (xmlroot != NULL) + { + xmltmp = xmlnode_get_child(xmlroot, "sender"); + if (xmltmp != NULL) + uin = xmlnode_get_data(xmltmp); + + xmltmp = xmlnode_get_child(xmlroot, "text"); + if (xmltmp != NULL) + message = xmlnode_get_data(xmltmp); + + if ((uin != NULL) && (message != NULL)) + serv_got_im(gc, uin, message, 0, time(NULL)); + + g_free(uin); + g_free(message); + xmlnode_free(xmlroot); + } + } + g_free(tagstr); + g_free(smsmsg); } break; default: { @@ -2963,7 +3003,7 @@ if (b == NULL) return 1; - if (!aim_sn_is_icq(userinfo->sn)) + if (!aim_snvalid_icq(userinfo->sn)) { if (strcmp(purple_buddy_get_name(b), userinfo->sn)) serv_got_alias(gc, purple_buddy_get_name(b), userinfo->sn); @@ -3574,7 +3614,7 @@ g_free(tmp); presence = purple_status_get_presence(status); - aim_srv_setidle(od, purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); + aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); if (od->icq) { aim_icq_reqofflinemsgs(od); @@ -4154,6 +4194,17 @@ account = purple_connection_get_account(gc); ret = 0; + if (od->icq && aim_snvalid_sms(name)) { + /* + * We're sending to a phone number and this is ICQ, + * so send the message as an SMS using aim_icq_sendsms() + */ + int ret; + purple_debug_info("oscar", "Sending SMS to %s.\n", name); + ret = aim_icq_sendsms(od, name, message, purple_account_get_username(account)); + return (ret >= 0 ? 1 : ret); + } + if (imflags & PURPLE_MESSAGE_AUTO_RESP) tmp1 = purple_str_sub_away_formatters(message, name); else @@ -4254,12 +4305,12 @@ /* * If we're IMing an SMS user or an ICQ user from an ICQ account, then strip HTML. */ - if (aim_sn_is_sms(name)) { + if (aim_snvalid_sms(name)) { /* Messaging an SMS (mobile) user */ tmp2 = purple_markup_strip_html(tmp1); is_html = FALSE; - } else if (aim_sn_is_icq(purple_account_get_username(account))) { - if (aim_sn_is_icq(name)) { + } else if (aim_snvalid_icq(purple_account_get_username(account))) { + if (aim_snvalid_icq(name)) { /* From ICQ to ICQ */ tmp2 = purple_markup_strip_html(tmp1); is_html = FALSE; @@ -4321,7 +4372,7 @@ void oscar_get_info(PurpleConnection *gc, const char *name) { OscarData *od = (OscarData *)gc->proto_data; - if (od->icq && aim_sn_is_icq(name)) + if (od->icq && aim_snvalid_icq(name)) aim_icq_getallinfo(od, name); else aim_locate_getinfoshort(od, name, 0x00000003); @@ -4566,7 +4617,7 @@ oscar_set_info_and_status(account, FALSE, NULL, TRUE, status); /* Set the ICQ status for ICQ accounts only */ - if (aim_sn_is_icq(purple_account_get_username(account))) + if (aim_snvalid_icq(purple_account_get_username(account))) oscar_set_status_icq(account, status); } @@ -5405,30 +5456,30 @@ const char *oscar_list_icon_icq(PurpleAccount *a, PurpleBuddy *b) { - if ((b == NULL) || (b->name == NULL) || aim_sn_is_sms(b->name)) + if ((b == NULL) || (b->name == NULL) || aim_snvalid_sms(b->name)) { - if (a == NULL || aim_sn_is_icq(purple_account_get_username(a))) + if (a == NULL || aim_snvalid_icq(purple_account_get_username(a))) return "icq"; else return "aim"; } - if (aim_sn_is_icq(b->name)) + if (aim_snvalid_icq(b->name)) return "icq"; return "aim"; } const char *oscar_list_icon_aim(PurpleAccount *a, PurpleBuddy *b) { - if ((b == NULL) || (b->name == NULL) || aim_sn_is_sms(b->name)) + if ((b == NULL) || (b->name == NULL) || aim_snvalid_sms(b->name)) { - if (a != NULL && aim_sn_is_icq(purple_account_get_username(a))) + if (a != NULL && aim_snvalid_icq(purple_account_get_username(a))) return "icq"; else return "aim"; } - if (aim_sn_is_icq(b->name)) + if (aim_snvalid_icq(b->name)) return "icq"; return "aim"; } @@ -5712,7 +5763,7 @@ g_return_val_if_fail(account != NULL, NULL); /* Used to flag some statuses as "user settable" or not */ - is_icq = aim_sn_is_icq(purple_account_get_username(account)); + is_icq = aim_snvalid_icq(purple_account_get_username(account)); /* Common status types */ /* Really the available message should only be settable for AIM accounts */ @@ -5920,7 +5971,7 @@ userinfo = aim_locate_finduserinfo(od, buddy->name); menu = NULL; - if (od->icq && aim_sn_is_icq(purple_buddy_get_name(buddy))) + if (od->icq && aim_snvalid_icq(purple_buddy_get_name(buddy))) { act = purple_menu_action_new(_("Get AIM Info"), PURPLE_CALLBACK(oscar_get_aim_info_cb), @@ -6515,7 +6566,7 @@ od = (OscarData *)gc->proto_data; } - return (od != NULL && od->icq && aim_sn_is_icq(purple_account_get_username(account))); + return (od != NULL && od->icq && aim_snvalid_icq(purple_account_get_username(account))); } /* TODO: Find somewhere to put this instead of including it in a bunch of places. diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/oscar.h --- a/libpurple/protocols/oscar/oscar.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.h Wed Sep 05 22:32:14 2007 +0000 @@ -1320,6 +1320,7 @@ int aim_icq_getsimpleinfo(OscarData *od, const char *uin); int aim_icq_getalias(OscarData *od, const char *uin); int aim_icq_getallinfo(OscarData *od, const char *uin); +int aim_icq_sendsms(OscarData *od, const char *name, const char *msg, const char *alias); @@ -1458,8 +1459,8 @@ char *aimutil_itemindex(char *toSearch, int theindex, char dl); gboolean aim_snvalid(const char *sn); -gboolean aim_sn_is_icq(const char *sn); -gboolean aim_sn_is_sms(const char *sn); +gboolean aim_snvalid_icq(const char *sn); +gboolean aim_snvalid_sms(const char *sn); int aim_snlen(const char *sn); int aim_sncmp(const char *sn1, const char *sn2); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/oscarcommon.h --- a/libpurple/protocols/oscar/oscarcommon.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/oscarcommon.h Wed Sep 05 22:32:14 2007 +0000 @@ -30,7 +30,7 @@ #include "version.h" #include "notify.h" -#define OSCAR_DEFAULT_LOGIN_SERVER "login.oscar.aol.com" +#define OSCAR_DEFAULT_LOGIN_SERVER "login.messaging.aol.com" #define OSCAR_DEFAULT_LOGIN_PORT 5190 #ifndef _WIN32 #define OSCAR_DEFAULT_CUSTOM_ENCODING "ISO-8859-1" diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/oscar/util.c --- a/libpurple/protocols/oscar/util.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/oscar/util.c Wed Sep 05 22:32:14 2007 +0000 @@ -171,7 +171,7 @@ * * @return TRUE if the screen name is valid, FALSE if not. */ -static gboolean +gboolean aim_snvalid_icq(const char *sn) { int i; @@ -190,7 +190,7 @@ * * @return TRUE if the screen name is valid, FALSE if not. */ -static gboolean +gboolean aim_snvalid_sms(const char *sn) { int i; @@ -221,34 +221,6 @@ } /** - * Determine if a given screen name is an ICQ screen name - * (i.e. it is composed of only numbers). - * - * @param sn A valid AIM or ICQ screen name. - * @return TRUE if the screen name is an ICQ screen name. Otherwise - * FALSE is returned. - */ -gboolean -aim_sn_is_icq(const char *sn) -{ - return aim_snvalid_icq(sn); -} - -/** - * Determine if a given screen name is an SMS number - * (i.e. it begins with a +). - * - * @param sn A valid AIM or ICQ screen name. - * @return TRUE if the screen name is an SMS number. Otherwise - * FALSE is returned. - */ -gboolean -aim_sn_is_sms(const char *sn) -{ - return (sn[0] == '+'); -} - -/** * This takes two screen names and compares them using the rules * on screen names for AIM/AOL. Mainly, this means case and space * insensitivity (all case differences and spacing differences are diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/qq/header_info.c --- a/libpurple/protocols/qq/header_info.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/qq/header_info.c Wed Sep 05 22:32:14 2007 +0000 @@ -35,6 +35,7 @@ #define QQ_CLIENT_0B35 0x0b35 /* GB QQ2003iii build 0304 (offical release) */ #define QQ_CLIENT_0B37 0x0b37 /* GB QQ2003iii build 0304 (April 05 updates) */ #define QQ_CLIENT_0E1B 0x0e1b /* QQ2005? QQ2006? */ +#define QQ_CLIENT_0E35 0x0e35 /* EN QQ2005 V05.0.200.020 */ #define QQ_CLIENT_0F15 0x0f15 /* QQ2006 Spring Festival build */ #define QQ_CLIENT_0F5F 0x0f5f /* QQ2006 final build */ @@ -115,6 +116,8 @@ return "GB QQ2003iii build 0304 (April 5 update)"; case QQ_CLIENT_0E1B: return "QQ2005 or QQ2006"; + case QQ_CLIENT_0E35: + return "En QQ2005 V05.0.200.020"; case QQ_CLIENT_0F15: return "QQ2006 Spring Festival build"; case QQ_CLIENT_0F5F: diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/qq/login_logout.c --- a/libpurple/protocols/qq/login_logout.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/qq/login_logout.c Wed Sep 05 22:32:14 2007 +0000 @@ -380,33 +380,33 @@ void qq_process_request_login_token_reply(guint8 *buf, gint buf_len, PurpleConnection *gc) { - qq_data *qd; + qq_data *qd; gchar *hex_dump; - g_return_if_fail(buf != NULL && buf_len != 0); + g_return_if_fail(buf != NULL && buf_len != 0); - qd = (qq_data *) gc->proto_data; + qd = (qq_data *) gc->proto_data; if (buf[0] == QQ_REQUEST_LOGIN_TOKEN_REPLY_OK) { if (buf[1] != buf_len-2) { - purple_debug(PURPLE_DEBUG_INFO, "QQ", + purple_debug(PURPLE_DEBUG_INFO, "QQ", "Malformed login token reply packet. Packet specifies length of %d, actual length is %d\n", buf[1], buf_len-2); purple_debug(PURPLE_DEBUG_INFO, "QQ", "Attempting to proceed with the actual packet length.\n"); } hex_dump = hex_dump_to_str(buf+2, buf_len-2); purple_debug(PURPLE_DEBUG_INFO, "QQ", - "<<< got a token with %d bytes -> [default] decrypt and dump\n%s", buf_len-2, hex_dump); + "<<< got a token with %d bytes -> [default] decrypt and dump\n%s", buf_len-2, hex_dump); qq_send_packet_login(gc, buf_len-2, buf+2); } else { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown request login token reply code : %d\n", buf[0]); hex_dump = hex_dump_to_str(buf, buf_len); - purple_debug(PURPLE_DEBUG_WARNING, "QQ", - ">>> %d bytes -> [default] decrypt and dump\n%s", - buf_len, hex_dump); - try_dump_as_gbk(buf, buf_len); + purple_debug(PURPLE_DEBUG_WARNING, "QQ", + ">>> %d bytes -> [default] decrypt and dump\n%s", + buf_len, hex_dump); + try_dump_as_gbk(buf, buf_len); purple_connection_error(gc, _("Error requesting login token")); - } + } g_free(hex_dump); } @@ -463,11 +463,11 @@ default: purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Unknown reply code: %d\n", data[0]); hex_dump = hex_dump_to_str(data, len); - purple_debug(PURPLE_DEBUG_WARNING, "QQ", - ">>> %d bytes -> [default] decrypt and dump\n%s", - buf_len, hex_dump); + purple_debug(PURPLE_DEBUG_WARNING, "QQ", + ">>> %d bytes -> [default] decrypt and dump\n%s", + buf_len, hex_dump); g_free(hex_dump); - try_dump_as_gbk(data, len); + try_dump_as_gbk(data, len); ret = QQ_LOGIN_REPLY_MISC_ERROR; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/silc/Makefile.mingw --- a/libpurple/protocols/silc/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/silc/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -8,8 +8,8 @@ include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak TARGET = libsilc -NEEDED_DLLS = $(SILC_TOOLKIT)/lib/silc.dll \ - $(SILC_TOOLKIT)/lib/silcclient.dll +NEEDED_DLLS = $(SILC_TOOLKIT)/bin/libsilc-1-1-2.dll \ + $(SILC_TOOLKIT)/bin/libsilcclient-1-1-2.dll TYPE = PLUGIN # Static or Plugin... diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/silc/silc.c Wed Sep 05 22:32:14 2007 +0000 @@ -1975,9 +1975,6 @@ silc_log_set_debug_string("*client*"); #endif -#ifdef _WIN32 - silc_net_win32_init(); -#endif } PURPLE_INIT_PLUGIN(silc, init_plugin, info); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Wed Sep 05 22:32:14 2007 +0000 @@ -969,7 +969,6 @@ PurpleConnection *gc; char *id; char *who; - char *msg; int protocol; }; @@ -987,7 +986,6 @@ g_free(add_req->id); g_free(add_req->who); - g_free(add_req->msg); g_free(add_req); } @@ -1018,7 +1016,6 @@ g_free(add_req->id); g_free(add_req->who); - g_free(add_req->msg); g_free(add_req); } @@ -1132,10 +1129,18 @@ l = l->next; } - if (add_req->id) { - char *alias = NULL; + if (add_req->id && add_req->who) { + char *alias = NULL, *dec_msg = NULL; + + if (!yahoo_privacy_check(gc, add_req->who)) { + purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n", + add_req->who); + yahoo_buddy_add_deny_cb(add_req, NULL); + return; + } + if (msg) - add_req->msg = yahoo_string_decode(gc, msg, FALSE); + dec_msg = yahoo_string_decode(gc, msg, FALSE); if (firstname && lastname) alias = g_strdup_printf("%s %s", firstname, lastname); @@ -1144,20 +1149,19 @@ else if (lastname) alias = g_strdup(lastname); - /* DONE! this is almost exactly the same as what MSN does, * this should probably be moved to the core. */ purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id, - alias, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL, + alias, dec_msg, purple_find_buddy(purple_connection_get_account(gc), add_req->who) != NULL, yahoo_buddy_add_authorize_cb, yahoo_buddy_add_deny_reason_cb, add_req); g_free(alias); + g_free(dec_msg); } else { g_free(add_req->id); g_free(add_req->who); - /*g_free(add_req->msg);*/ g_free(add_req); } } else { @@ -1165,6 +1169,7 @@ } } +/* I don't think this happens anymore in Version 15 */ static void yahoo_buddy_added_us(PurpleConnection *gc, struct yahoo_packet *pkt) { struct yahoo_add_request *add_req; char *msg = NULL; @@ -1192,22 +1197,31 @@ l = l->next; } - if (add_req->id) { + if (add_req->id && add_req->who) { + char *dec_msg = NULL; + + if (!yahoo_privacy_check(gc, add_req->who)) { + purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n", + add_req->who); + yahoo_buddy_add_deny_cb(add_req, NULL); + return; + } + if (msg) - add_req->msg = yahoo_string_decode(gc, msg, FALSE); + dec_msg = yahoo_string_decode(gc, msg, FALSE); /* DONE! this is almost exactly the same as what MSN does, * this should probably be moved to the core. */ purple_account_request_authorization(purple_connection_get_account(gc), add_req->who, add_req->id, - NULL, add_req->msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL, + NULL, dec_msg, purple_find_buddy(purple_connection_get_account(gc),add_req->who) != NULL, yahoo_buddy_add_authorize_cb, yahoo_buddy_add_deny_reason_cb, add_req); + g_free(dec_msg); } else { g_free(add_req->id); g_free(add_req->who); - /*g_free(add_req->msg);*/ g_free(add_req); } } @@ -3016,6 +3030,11 @@ if (yd->ycht) ycht_connection_close(yd->ycht); + g_free(yd->pending_chat_room); + g_free(yd->pending_chat_id); + g_free(yd->pending_chat_topic); + g_free(yd->pending_chat_goto); + g_free(yd); gc->proto_data = NULL; } @@ -4096,9 +4115,7 @@ purple_debug(PURPLE_DEBUG_INFO, "yahoo", "Sending on account %s to buddy %s.\n", username, c->name); - /* TODO: find out how to send a without showing up as a blank line on - * the conversation window. */ - purple_conv_im_send(PURPLE_CONV_IM(c), ""); + purple_conv_im_send_with_flags(PURPLE_CONV_IM(c), "", PURPLE_MESSAGE_INVISIBLE); return TRUE; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/yahoo/yahoo.h --- a/libpurple/protocols/yahoo/yahoo.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.h Wed Sep 05 22:32:14 2007 +0000 @@ -130,6 +130,10 @@ gboolean chat_online; gboolean in_chat; char *chat_name; + char *pending_chat_room; + char *pending_chat_id; + char *pending_chat_topic; + char *pending_chat_goto; char *auth; gsize auth_written; char *cookie_y; diff -r b0733d5d7621 -r 1d2002a5735e libpurple/protocols/yahoo/yahoochat.c --- a/libpurple/protocols/yahoo/yahoochat.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoochat.c Wed Sep 05 22:32:14 2007 +0000 @@ -55,16 +55,24 @@ { struct yahoo_data *yd = gc->proto_data; struct yahoo_packet *pkt; + const char *rll; if (yd->wm) { ycht_connection_open(gc); return; } + rll = purple_account_get_string(purple_connection_get_account(gc), + "room_list_locale", YAHOO_ROOMLIST_LOCALE); + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATONLINE, YAHOO_STATUS_AVAILABLE,0); - yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), - 109, purple_connection_get_display_name(gc), 6, "abcde", - 135, "ym8.1.0.415"); + yahoo_packet_hash(pkt, "sssss", + 109, purple_connection_get_display_name(gc), + 1, purple_connection_get_display_name(gc), + 6, "abcde", + /* I'm not sure this is the correct way to set this. */ + 98, rll, + 135, "ym8.1.0.415"); yahoo_packet_send_and_free(pkt, yd); } @@ -125,6 +133,7 @@ case 1: /* us, but we already know who we are */ break; case 57: + g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 50: /* inviter */ @@ -136,6 +145,7 @@ g_string_append_printf(members, "%s\n", pair->value); break; case 58: + g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; case 13: /* ? */ @@ -145,6 +155,17 @@ if (!room) { g_string_free(members, TRUE); + g_free(msg); + return; + } + + if (!yahoo_privacy_check(gc, who) || + (purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) { + purple_debug_info("yahoo", + "Invite to conference %s from %s has been dropped.\n", room, who); + g_free(room); + g_free(msg); + g_string_free(members, TRUE); return; } @@ -153,19 +174,9 @@ if (msg) g_hash_table_replace(components, g_strdup("topic"), msg); g_hash_table_replace(components, g_strdup("type"), g_strdup("Conference")); - if (members) { - g_hash_table_replace(components, g_strdup("members"), g_strdup(members->str)); - } - if (!yahoo_privacy_check(gc, who) || - (purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) { - purple_debug_info("yahoo", - "Invite to conference %s from %s has been dropped.\n", room, who); - g_string_free(members, TRUE); - return; - } + g_hash_table_replace(components, g_strdup("members"), g_string_free(members, FALSE)); serv_got_chat_invite(gc, room, who, msg, components); - g_string_free(members, TRUE); } void yahoo_process_conference_decline(PurpleConnection *gc, struct yahoo_packet *pkt) @@ -180,20 +191,21 @@ switch (pair->key) { case 57: + g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 54: who = pair->value; break; case 14: + g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; } } if (!yahoo_privacy_check(gc, who)) { g_free(room); - if (msg != NULL) - g_free(msg); + g_free(msg); return; } @@ -209,8 +221,7 @@ } g_free(room); - if (msg) - g_free(msg); + g_free(msg); } } @@ -226,6 +237,7 @@ switch (pair->key) { case 57: + g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 53: @@ -254,6 +266,7 @@ switch (pair->key) { case 57: + g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 56: @@ -276,7 +289,6 @@ char *room = NULL; char *who = NULL; char *msg = NULL; - char *msg2; int utf8 = 0; PurpleConversation *c; @@ -285,6 +297,7 @@ switch (pair->key) { case 57: + g_free(room); room = yahoo_string_decode(gc, pair->value, FALSE); break; case 3: @@ -299,28 +312,82 @@ } } - if (room && who && msg) { - msg2 = yahoo_string_decode(gc, msg, utf8); - c = yahoo_find_conference(gc, room); - if (!c) - return; - msg = yahoo_codes_to_html(msg2); - serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL)); - g_free(msg); - g_free(msg2); + if (room && who && msg) { + char *msg2; + + c = yahoo_find_conference(gc, room); + if (!c) { + g_free(room); + return; } - if (room) - g_free(room); + + msg2 = yahoo_string_decode(gc, msg, utf8); + msg = yahoo_codes_to_html(msg2); + serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), who, 0, msg, time(NULL)); + g_free(msg); + g_free(msg2); + } + + g_free(room); } +static void yahoo_chat_join(PurpleConnection *gc, const char *dn, const char *room, const char *topic, const char *id) +{ + struct yahoo_data *yd = gc->proto_data; + struct yahoo_packet *pkt; + char *room2; + gboolean utf8 = TRUE; + + if (yd->wm) { + g_return_if_fail(yd->ycht != NULL); + ycht_chat_join(yd->ycht, room); + return; + } + + /* apparently room names are always utf8, or else always not utf8, + * so we don't have to actually pass the flag in the packet. Or something. */ + room2 = yahoo_string_encode(gc, room, &utf8); + + pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, "ssss", + 1, purple_connection_get_display_name(gc), + 104, room2, + 62, "2", + 129, id ? id : "0"); + yahoo_packet_send_and_free(pkt, yd); + g_free(room2); +} /* this is a confirmation of yahoo_chat_online(); */ void yahoo_process_chat_online(PurpleConnection *gc, struct yahoo_packet *pkt) { struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data; - if (pkt->status == 1) + if (pkt->status == 1) { yd->chat_online = 1; + + /* We need to goto a user in chat */ + if (yd->pending_chat_goto) { + struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pkt, "sss", + 109, yd->pending_chat_goto, + 1, purple_connection_get_display_name(gc), + 62, "2"); + yahoo_packet_send_and_free(pkt, yd); + } else if (yd->pending_chat_room) { + yahoo_chat_join(gc, purple_connection_get_display_name(gc), yd->pending_chat_room, + yd->pending_chat_topic, yd->pending_chat_id); + } + + g_free(yd->pending_chat_room); + yd->pending_chat_room = NULL; + g_free(yd->pending_chat_id); + yd->pending_chat_id = NULL; + g_free(yd->pending_chat_topic); + yd->pending_chat_topic = NULL; + g_free(yd->pending_chat_goto); + yd->pending_chat_goto = NULL; + } } /* this is basicly the opposite of chat_online */ @@ -340,6 +407,14 @@ if (pkt->status == 1) { yd->chat_online = 0; + g_free(yd->pending_chat_room); + yd->pending_chat_room = NULL; + g_free(yd->pending_chat_id); + yd->pending_chat_id = NULL; + g_free(yd->pending_chat_topic); + yd->pending_chat_topic = NULL; + g_free(yd->pending_chat_goto); + yd->pending_chat_goto = NULL; if (yd->in_chat) yahoo_c_leave(gc, YAHOO_CHAT_ID); } @@ -384,9 +459,11 @@ switch (pair->key) { case 104: + g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 105: + g_free(topic); topic = yahoo_string_decode(gc, pair->value, TRUE); break; case 128: @@ -445,8 +522,11 @@ purple_conversation_set_name(c, room); c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room); - if (topic) + if (topic) { purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); + /* Also print the topic to the backlog so that the captcha link is clickable */ + purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL)); + } yd->in_chat = 1; yd->chat_name = g_strdup(room); purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE); @@ -456,14 +536,22 @@ g_free(tmpmsg); } else { c = serv_got_joined_chat(gc, YAHOO_CHAT_ID, room); - if (topic) + if (topic) { purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); + /* Also print the topic to the backlog so that the captcha link is clickable */ + purple_conv_chat_write(PURPLE_CONV_CHAT(c), "", topic, PURPLE_MESSAGE_SYSTEM, time(NULL)); + } yd->in_chat = 1; yd->chat_name = g_strdup(room); purple_conv_chat_add_users(PURPLE_CONV_CHAT(c), members, NULL, flags, FALSE); } g_list_free(flags); } else if (c) { + if (topic) { + const char *cur_topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(c)); + if (cur_topic == NULL || strcmp(cur_topic, topic) != 0) + purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), NULL, topic); + } yahoo_chat_add_users(PURPLE_CONV_CHAT(c), members); } @@ -497,8 +585,10 @@ for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; - if (pair->key == 104) + if (pair->key == 104) { + g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); + } if (pair->key == 109) who = pair->value; } @@ -529,6 +619,7 @@ utf8 = strtol(pair->value, NULL, 10); break; case 104: + g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 109: @@ -583,6 +674,7 @@ switch (pair->key) { case 104: + g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); break; case 129: /* room id? */ @@ -590,6 +682,7 @@ case 126: /* ??? */ break; case 117: + g_free(msg); msg = yahoo_string_decode(gc, pair->value, FALSE); break; case 119: @@ -603,24 +696,21 @@ if (room && who) { GHashTable *components; + if (!yahoo_privacy_check(gc, who) || + (purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) { + purple_debug_info("yahoo", "Invite to room %s from %s has been dropped.\n", room, who); + g_free(room); + g_free(msg); + return; + } + components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_replace(components, g_strdup("room"), g_strdup(room)); - if (!yahoo_privacy_check(gc, who) || - (purple_account_get_bool(purple_connection_get_account(gc), "ignore_invites", FALSE))) { - purple_debug_info("yahoo", - "Invite to room %s from %s has been dropped.\n", room, who); - if (room != NULL) - g_free(room); - if (msg != NULL) - g_free(msg); - return; - } serv_got_chat_invite(gc, room, who, msg, components); } - if (room) - g_free(room); - if (msg) - g_free(msg); + + g_free(room); + g_free(msg); } void yahoo_process_chat_goto(PurpleConnection *gc, struct yahoo_packet *pkt) @@ -783,6 +873,14 @@ yahoo_packet_send_and_free(pkt, yd); yd->chat_online = 0; + g_free(yd->pending_chat_room); + yd->pending_chat_room = NULL; + g_free(yd->pending_chat_id); + yd->pending_chat_id = NULL; + g_free(yd->pending_chat_topic); + yd->pending_chat_topic = NULL; + g_free(yd->pending_chat_goto); + yd->pending_chat_goto = NULL; g_free(eroom); } @@ -829,29 +927,6 @@ return 0; } -static void yahoo_chat_join(PurpleConnection *gc, const char *dn, const char *room, const char *topic) -{ - struct yahoo_data *yd = gc->proto_data; - struct yahoo_packet *pkt; - char *room2; - gboolean utf8 = TRUE; - - if (yd->wm) { - g_return_if_fail(yd->ycht != NULL); - ycht_chat_join(yd->ycht, room); - return; - } - - /* apparently room names are always utf8, or else always not utf8, - * so we don't have to actually pass the flag in the packet. Or something. */ - room2 = yahoo_string_encode(gc, room, &utf8); - - pkt = yahoo_packet_new(YAHOO_SERVICE_CHATJOIN, YAHOO_STATUS_AVAILABLE, 0); - yahoo_packet_hash(pkt, "ssss", 1, purple_connection_get_display_name(gc), - 62, "2", 104, room2, 129, "0"); - yahoo_packet_send_and_free(pkt, yd); - g_free(room2); -} static void yahoo_chat_invite(PurpleConnection *gc, const char *dn, const char *buddy, const char *room, const char *msg) @@ -892,8 +967,18 @@ return; } - if (!yd->chat_online) + if (!yd->chat_online) { yahoo_chat_online(gc); + g_free(yd->pending_chat_room); + yd->pending_chat_room = NULL; + g_free(yd->pending_chat_id); + yd->pending_chat_id = NULL; + g_free(yd->pending_chat_topic); + yd->pending_chat_topic = NULL; + g_free(yd->pending_chat_goto); + yd->pending_chat_goto = g_strdup(name); + return; + } pkt = yahoo_packet_new(YAHOO_SERVICE_CHATGOTO, YAHOO_STATUS_AVAILABLE, 0); yahoo_packet_hash(pkt, "sss", 109, name, 1, purple_connection_get_display_name(gc), 62, "2"); @@ -988,8 +1073,7 @@ void yahoo_c_join(PurpleConnection *gc, GHashTable *data) { struct yahoo_data *yd; - char *room, *topic, *members, *type; - int id; + char *room, *topic, *type; PurpleConversation *c; yd = (struct yahoo_data *) gc->proto_data; @@ -1004,9 +1088,9 @@ if (!topic) topic = ""; - members = g_hash_table_lookup(data, "members"); - if ((type = g_hash_table_lookup(data, "type")) && !strcmp(type, "Conference")) { + int id; + const char *members = g_hash_table_lookup(data, "members"); id = yd->conf_id++; c = serv_got_joined_chat(gc, id, room); yd->confs = g_slist_prepend(yd->confs, c); @@ -1014,13 +1098,27 @@ yahoo_conf_join(yd, c, purple_connection_get_display_name(gc), room, topic, members); return; } else { - if (yd->in_chat) + const char *id; + /*if (yd->in_chat) yahoo_chat_leave(gc, room, purple_connection_get_display_name(gc), - FALSE); - if (!yd->chat_online) + FALSE);*/ + + id = g_hash_table_lookup(data, "id"); + + if (!yd->chat_online) { yahoo_chat_online(gc); - yahoo_chat_join(gc, purple_connection_get_display_name(gc), room, topic); + g_free(yd->pending_chat_room); + yd->pending_chat_room = g_strdup(room); + g_free(yd->pending_chat_id); + yd->pending_chat_id = g_strdup(id); + g_free(yd->pending_chat_topic); + yd->pending_chat_topic = g_strdup(topic); + g_free(yd->pending_chat_goto); + yd->pending_chat_goto = NULL; + } else { + yahoo_chat_join(gc, purple_connection_get_display_name(gc), room, topic, id); + } return; } } @@ -1148,16 +1246,13 @@ for (i = 0; anames[i]; i++) { if (!strcmp(anames[i], "id")) { - if (s->room.id) - g_free(s->room.id); + g_free(s->room.id); s->room.id = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "name")) { - if (s->room.name) - g_free(s->room.name); + g_free(s->room.name); s->room.name = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "topic")) { - if (s->room.topic) - g_free(s->room.topic); + g_free(s->room.topic); s->room.topic = g_strdup(avalues[i]); } else if (!strcmp(anames[i], "type")) { if (!strcmp("yahoo", avalues[i])) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/prpl.c --- a/libpurple/prpl.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/prpl.c Wed Sep 05 22:32:14 2007 +0000 @@ -199,8 +199,10 @@ if(NULL == status) continue; - purple_status_set_active(status, FALSE); - purple_blist_update_buddy_status(buddy, status); + if (purple_status_is_active(status)) { + purple_status_set_active(status, FALSE); + purple_blist_update_buddy_status(buddy, status); + } } g_slist_free(list); diff -r b0733d5d7621 -r 1d2002a5735e libpurple/server.c --- a/libpurple/server.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/server.c Wed Sep 05 22:32:14 2007 +0000 @@ -272,6 +272,7 @@ PurpleAttentionType *attn; PurpleMessageFlags flags; PurplePlugin *prpl; + PurpleConversation *conv; gboolean (*send_attention)(PurpleConnection *, const char *, guint); gchar *description; @@ -302,8 +303,8 @@ if (!send_attention(gc, who, type_code)) return; - /* TODO: icons, sound, shaking... same as serv_got_attention(). */ - serv_got_im(gc, who, description, flags, mtime); + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, gc->account, who); + purple_conv_im_write(PURPLE_CONV_IM(conv), NULL, description, flags, mtime); g_free(description); } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/sslconn.c --- a/libpurple/sslconn.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/sslconn.c Wed Sep 05 22:32:14 2007 +0000 @@ -24,6 +24,7 @@ */ #include "internal.h" +#include "certificate.h" #include "debug.h" #include "sslconn.h" @@ -117,6 +118,9 @@ gsc->connect_cb = func; gsc->error_cb = error_func; + /* TODO: Move this elsewhere */ + gsc->verifier = purple_certificate_find_verifier("x509","tls_cached"); + gsc->connect_data = purple_proxy_connect(NULL, account, host, port, purple_ssl_connect_cb, gsc); if (gsc->connect_data == NULL) @@ -151,17 +155,33 @@ gsc->inpa = purple_input_add(gsc->fd, PURPLE_INPUT_READ, recv_cb, gsc); } +const gchar * +purple_ssl_strerror(PurpleSslErrorType error) +{ + switch(error) { + case PURPLE_SSL_CONNECT_FAILED: + return _("SSL Connection Failed"); + case PURPLE_SSL_HANDSHAKE_FAILED: + return _("SSL Handshake Failed"); + case PURPLE_SSL_CERTIFICATE_INVALID: + return _("SSL peer presented an invalid certificate"); + default: + purple_debug_warning("sslconn", "Unknown SSL error code %d\n", error); + return _("Unknown SSL error"); + } +} + PurpleSslConnection * purple_ssl_connect_fd(PurpleAccount *account, int fd, PurpleSslInputFunction func, PurpleSslErrorFunction error_func, void *data) { - return purple_ssl_connect_fd_with_host(account, fd, func, error_func, NULL, data); + return purple_ssl_connect_with_host_fd(account, fd, func, error_func, NULL, data); } PurpleSslConnection * -purple_ssl_connect_fd_with_host(PurpleAccount *account, int fd, +purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd, PurpleSslInputFunction func, PurpleSslErrorFunction error_func, const char *host, @@ -189,6 +209,10 @@ if(host) gsc->host = g_strdup(host); + /* TODO: Move this elsewhere */ + gsc->verifier = purple_certificate_find_verifier("x509","tls_cached"); + + ops = purple_ssl_get_ops(); ops->connectfunc(gsc); @@ -244,6 +268,17 @@ return (ops->write)(gsc, data, len); } +GList * +purple_ssl_get_peer_certificates(PurpleSslConnection *gsc) +{ + PurpleSslOps *ops; + + g_return_val_if_fail(gsc != NULL, NULL); + + ops = purple_ssl_get_ops(); + return (ops->get_peer_certificates)(gsc); +} + void purple_ssl_set_ops(PurpleSslOps *ops) { @@ -259,8 +294,12 @@ void purple_ssl_init(void) { - /* This doesn't do anything at the moment. All the actual init work - * is handled by purple_ssl_is_supported upon demand. */ + /* Although purple_ssl_is_supported will do the initialization on + command, SSL plugins tend to register CertificateSchemes as well + as providing SSL ops. */ + if (!ssl_init()) { + purple_debug_error("sslconn", "Unable to initialize SSL.\n"); + } } void diff -r b0733d5d7621 -r 1d2002a5735e libpurple/sslconn.h --- a/libpurple/sslconn.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/sslconn.h Wed Sep 05 22:32:14 2007 +0000 @@ -25,6 +25,7 @@ #ifndef _PURPLE_SSLCONN_H_ #define _PURPLE_SSLCONN_H_ +#include "certificate.h" #include "proxy.h" #define PURPLE_SSL_DEFAULT_PORT 443 @@ -32,7 +33,8 @@ typedef enum { PURPLE_SSL_HANDSHAKE_FAILED = 1, - PURPLE_SSL_CONNECT_FAILED = 2 + PURPLE_SSL_CONNECT_FAILED = 2, + PURPLE_SSL_CERTIFICATE_INVALID = 3 } PurpleSslErrorType; typedef struct _PurpleSslConnection PurpleSslConnection; @@ -69,6 +71,9 @@ /** Internal connection data managed by the SSL backend (GnuTLS/LibNSS/whatever) */ void *private_data; + + /** Verifier to use in authenticating the peer */ + PurpleCertificateVerifier *verifier; }; /** @@ -107,8 +112,17 @@ * @return The number of bytes written (may be less than len) or <0 on error */ size_t (*write)(PurpleSslConnection *gsc, const void *data, size_t len); - - void (*_purple_reserved1)(void); + /** Obtains the certificate chain provided by the peer + * + * @param gsc Connection context + * @return A newly allocated list containing the certificates + * the peer provided. + * @see PurpleCertificate + * @todo Decide whether the ordering of certificates in this + * list can be guaranteed. + */ + GList * (* get_peer_certificates)(PurpleSslConnection * gsc); + void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); void (*_purple_reserved4)(void); @@ -131,6 +145,14 @@ gboolean purple_ssl_is_supported(void); /** + * Returns a human-readable string for an SSL error + * + * @param error Error code + * @return Human-readable error explanation + */ +const gchar * purple_ssl_strerror(PurpleSslErrorType error); + +/** * Makes a SSL connection to the specified host and port. The caller * should keep track of the returned value and use it to cancel the * connection, if needed. @@ -154,7 +176,7 @@ /** * Makes a SSL connection using an already open file descriptor. - * @deprecated Use purple_ssl_connect_fd_with_host instead. + * DEPRECATED. Use purple_ssl_connect_with_host_fd instead. * * @param account The account making the connection. * @param fd The file descriptor. @@ -181,7 +203,7 @@ * * @return The SSL connection handle. */ -PurpleSslConnection *purple_ssl_connect_fd_with_host(PurpleAccount *account, int fd, +PurpleSslConnection *purple_ssl_connect_with_host_fd(PurpleAccount *account, int fd, PurpleSslInputFunction func, PurpleSslErrorFunction error_func, const char *host, @@ -227,6 +249,16 @@ */ size_t purple_ssl_write(PurpleSslConnection *gsc, const void *buffer, size_t len); +/** + * Obtains the peer's presented certificates + * + * @param gsc The SSL connection handle + * + * @return The peer certificate chain, in the order of certificate, issuer, + * issuer's issuer, etc. NULL if no certificates have been provided, + */ +GList * purple_ssl_get_peer_certificates(PurpleSslConnection *gsc); + /*@}*/ /**************************************************************************/ diff -r b0733d5d7621 -r 1d2002a5735e libpurple/stun.c --- a/libpurple/stun.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/stun.c Wed Sep 05 22:32:14 2007 +0000 @@ -252,7 +252,7 @@ sinptr = (struct sockaddr_in *) &ifr->ifr_addr; if(sinptr->sin_addr.s_addr == in.s_addr) { /* no NAT */ - purple_debug_info("stun", "no nat"); + purple_debug_info("stun", "no nat\n"); nattype.type = PURPLE_STUN_NAT_TYPE_PUBLIC_IP; } } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/util.c --- a/libpurple/util.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/util.c Wed Sep 05 22:32:14 2007 +0000 @@ -2542,10 +2542,8 @@ purple_util_write_data_to_file(const char *filename, const char *data, gssize size) { const char *user_dir = purple_user_dir(); - gchar *filename_temp, *filename_full; - FILE *file; - size_t real_size, byteswritten; - struct stat st; + gchar *filename_full; + gboolean ret = FALSE; g_return_val_if_fail(user_dir != NULL, FALSE); @@ -2564,6 +2562,25 @@ } filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename); + + ret = purple_util_write_data_to_file_absolute(filename_full, + data,size); + + g_free(filename_full); + return ret; +} + +gboolean +purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size) +{ + gchar *filename_temp; + FILE *file; + size_t real_size, byteswritten; + struct stat st; + + purple_debug_info("util", "Writing file %s\n", + filename_full); + filename_temp = g_strdup_printf("%s.save", filename_full); /* Remove an old temporary file, if one exists */ @@ -2571,8 +2588,9 @@ { if (g_unlink(filename_temp) == -1) { - purple_debug_error("util", "Error removing old file %s: %s\n", - filename_temp, strerror(errno)); + purple_debug_error("util", "Error removing old file " + "%s: %s\n", + filename_temp, strerror(errno)); } } @@ -2580,9 +2598,9 @@ file = g_fopen(filename_temp, "wb"); if (file == NULL) { - purple_debug_error("util", "Error opening file %s for writing: %s\n", - filename_temp, strerror(errno)); - g_free(filename_full); + purple_debug_error("util", "Error opening file %s for " + "writing: %s\n", + filename_temp, strerror(errno)); g_free(filename_temp); return FALSE; } @@ -2595,8 +2613,7 @@ if (fclose(file) != 0) { purple_debug_error("util", "Error closing file %s: %s\n", - filename_temp, strerror(errno)); - g_free(filename_full); + filename_temp, strerror(errno)); g_free(filename_temp); return FALSE; } @@ -2604,10 +2621,11 @@ /* Ensure the file is the correct size */ if (byteswritten != real_size) { - purple_debug_error("util", "Error writing to file %s: Wrote %" G_GSIZE_FORMAT " bytes " - "but should have written %" G_GSIZE_FORMAT "; is your disk full?\n", - filename_temp, byteswritten, real_size); - g_free(filename_full); + purple_debug_error("util", "Error writing to file %s: Wrote %" + G_GSIZE_FORMAT " bytes " + "but should have written %" G_GSIZE_FORMAT + "; is your disk full?\n", + filename_temp, byteswritten, real_size); g_free(filename_temp); return FALSE; } @@ -2615,9 +2633,9 @@ if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size)) { purple_debug_error("util", "Error writing data to file %s: " - "Incomplete file written; is your disk full?\n", - filename_temp); - g_free(filename_full); + "Incomplete file written; is your disk " + "full?\n", + filename_temp); g_free(filename_temp); return FALSE; } @@ -2635,10 +2653,10 @@ if (g_rename(filename_temp, filename_full) == -1) { purple_debug_error("util", "Error renaming %s to %s: %s\n", - filename_temp, filename_full, strerror(errno)); + filename_temp, filename_full, + strerror(errno)); } - g_free(filename_full); g_free(filename_temp); return TRUE; @@ -4442,10 +4460,11 @@ const char *_purple_oscar_convert(const char *act, const char *protocol) { if (protocol && act && strcmp(protocol, "prpl-oscar") == 0) { - if (isdigit(*act)) - protocol = "prpl-icq"; - else - protocol = "prpl-aim"; + int i; + for (i = 0; act[i] != '\0'; i++) + if (!isdigit(act[i])) + return "prpl-aim"; + return "prpl-icq"; } return protocol; } diff -r b0733d5d7621 -r 1d2002a5735e libpurple/util.h --- a/libpurple/util.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/util.h Wed Sep 05 22:32:14 2007 +0000 @@ -589,6 +589,26 @@ gssize size); /** + * Write data to a file using the absolute path. + * + * This exists for Glib backwards compatibility reasons. + * + * @param filename_full Filename to write to + * @param data A null-terminated string of data to write. + * @param size The size of the data to save. If data is + * null-terminated you can pass in -1. + * + * @return TRUE if the file was written successfully. FALSE otherwise. + * + * @todo Remove this function (use g_file_set_contents instead) when 3.0.0 + * rolls around. + * @see purple_util_write_data_to_file() + * + */ +gboolean +purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, size_t size); + +/** * Read the contents of a given file and parse the results into an * xmlnode tree structure. This is intended to be used to read * Purple's configuration xml files (prefs.xml, pounces.xml, etc.) diff -r b0733d5d7621 -r 1d2002a5735e libpurple/value.h --- a/libpurple/value.h Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/value.h Wed Sep 05 22:32:14 2007 +0000 @@ -77,7 +77,8 @@ PURPLE_SUBTYPE_SAVEDSTATUS, PURPLE_SUBTYPE_XMLNODE, PURPLE_SUBTYPE_USERINFO, - PURPLE_SUBTYPE_STORED_IMAGE + PURPLE_SUBTYPE_STORED_IMAGE, + PURPLE_SUBTYPE_CERTIFICATEPOOL } PurpleSubType; /** diff -r b0733d5d7621 -r 1d2002a5735e libpurple/win32/global.mak --- a/libpurple/win32/global.mak Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/win32/global.mak Wed Sep 05 22:32:14 2007 +0000 @@ -20,7 +20,7 @@ NSPR_TOP ?= $(WIN32_DEV_TOP)/nspr-4.6.4 NSS_TOP ?= $(WIN32_DEV_TOP)/nss-3.11.4 PERL_LIB_TOP ?= $(WIN32_DEV_TOP)/perl58 -SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.0.2 +SILC_TOOLKIT ?= $(WIN32_DEV_TOP)/silc-toolkit-1.1.2 TCL_LIB_TOP ?= $(WIN32_DEV_TOP)/tcl-8.4.5 # Where we installing this stuff to? diff -r b0733d5d7621 -r 1d2002a5735e libpurple/xmlnode.c --- a/libpurple/xmlnode.c Tue Aug 28 19:03:07 2007 +0000 +++ b/libpurple/xmlnode.c Wed Sep 05 22:32:14 2007 +0000 @@ -549,7 +549,16 @@ xmlnode_parser_error_libxml(void *user_data, const char *msg, ...) { struct _xmlnode_parser_data *xpd = user_data; + char errmsg[2048]; + va_list args; + xpd->error = TRUE; + + va_start(args, msg); + vsnprintf(errmsg, sizeof(errmsg), msg, args); + va_end(args); + + purple_debug_error("xmlnode", "Error parsing xml file: %s\n", errmsg); } static xmlSAXHandler xmlnode_parser_libxml = { diff -r b0733d5d7621 -r 1d2002a5735e pidgin/Makefile.am --- a/pidgin/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -81,6 +81,7 @@ gtkcellrendererprogress.c \ gtkcellview.c \ gtkcellviewmenuitem.c \ + gtkcertmgr.c \ gtkconn.c \ gtkconv.c \ gtkdebug.c \ @@ -128,6 +129,7 @@ gtkcellviewmenuitem.h \ gtkcellview.h \ gtkcellviewmenuitem.h \ + gtkcertmgr.h \ pidgincombobox.h \ gtkconn.h \ gtkconv.h \ diff -r b0733d5d7621 -r 1d2002a5735e pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -56,10 +56,11 @@ PIDGIN_C_SRC = \ gtkaccount.c \ gtkblist.c \ + gtkcertmgr.c \ + gtkcellrendererexpander.c \ + gtkcellrendererprogress.c \ gtkconn.c \ gtkconv.c \ - gtkcellrendererexpander.c \ - gtkcellrendererprogress.c \ gtkdebug.c \ gtkdialogs.c \ gtkdnd-hints.c \ diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkaccount.c Wed Sep 05 22:32:14 2007 +0000 @@ -136,9 +136,6 @@ GtkWidget *proxy_user_entry; GtkWidget *proxy_pass_entry; - /* Are we registering? */ - gboolean registering; - } AccountPrefsDialog; typedef struct @@ -655,8 +652,10 @@ if (!(dialog->prpl_info->options & OPT_PROTO_MAIL_CHECK)) gtk_widget_hide(dialog->new_mail_check); - if (dialog->prpl_info->icon_spec.format == NULL) + if (dialog->prpl_info->icon_spec.format == NULL) { + gtk_widget_hide(dialog->icon_check); gtk_widget_hide(dialog->icon_hbox); + } } if (dialog->account != NULL) { @@ -1147,7 +1146,7 @@ account_win_destroy_cb(NULL, NULL, dialog); } -static PurpleAccount* +static void ok_account_prefs_cb(GtkWidget *w, AccountPrefsDialog *dialog) { PurpleProxyInfo *proxy_info = NULL; @@ -1155,22 +1154,58 @@ const char *value; char *username; char *tmp; - gboolean new = FALSE, icon_change = FALSE; + gboolean new_acct = FALSE, icon_change = FALSE; PurpleAccount *account; PurplePluginProtocolInfo *prpl_info; + /* Build the username string. */ + username = g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry))); + + if (dialog->prpl_info != NULL) + { + for (l = dialog->prpl_info->user_splits, + l2 = dialog->user_split_entries; + l != NULL && l2 != NULL; + l = l->next, l2 = l2->next) + { + PurpleAccountUserSplit *split = l->data; + GtkEntry *entry = l2->data; + char sep[2] = " "; + + value = gtk_entry_get_text(entry); + + *sep = purple_account_user_split_get_separator(split); + + tmp = g_strconcat(username, sep, + (*value ? value : + purple_account_user_split_get_default_value(split)), + NULL); + + g_free(username); + username = tmp; + } + } + if (dialog->account == NULL) { - const char *screenname; + if (purple_accounts_find(username, dialog->protocol_id) != NULL) { + purple_debug_warning("gtkaccount", "Trying to add a duplicate %s account (%s).\n", + dialog->protocol_id, username); + + purple_notify_error(NULL, NULL, _("Unable to save new account"), + _("An account already exists with the specified criteria.")); + + g_free(username); + return; + } if (purple_accounts_get_all() == NULL) { /* We're adding our first account. Be polite and show the buddy list */ purple_blist_set_visible(TRUE); } - screenname = gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry)); - account = purple_account_new(screenname, dialog->protocol_id); - new = TRUE; + account = purple_account_new(username, dialog->protocol_id); + new_acct = TRUE; } else { @@ -1194,7 +1229,7 @@ { const char *filename; - if (new || purple_account_get_bool(account, "use-global-buddyicon", TRUE) == + if (new_acct || purple_account_get_bool(account, "use-global-buddyicon", TRUE) == gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->icon_check))) { icon_change = TRUE; @@ -1247,40 +1282,11 @@ * the account editor (but has not checked the 'save' box), then we * don't want to prompt them. */ - if ((purple_account_get_remember_password(account) || new) && (*value != '\0')) + if ((purple_account_get_remember_password(account) || new_acct) && (*value != '\0')) purple_account_set_password(account, value); else purple_account_set_password(account, NULL); - /* Build the username string. */ - username = - g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog->screenname_entry))); - - if (dialog->prpl_info != NULL) - { - for (l = dialog->prpl_info->user_splits, - l2 = dialog->user_split_entries; - l != NULL && l2 != NULL; - l = l->next, l2 = l2->next) - { - PurpleAccountUserSplit *split = l->data; - GtkEntry *entry = l2->data; - char sep[2] = " "; - - value = gtk_entry_get_text(entry); - - *sep = purple_account_user_split_get_separator(split); - - tmp = g_strconcat(username, sep, - (*value ? value : - purple_account_user_split_get_default_value(split)), - NULL); - - g_free(username); - username = tmp; - } - } - purple_account_set_username(account, username); g_free(username); @@ -1389,7 +1395,7 @@ } /* If this is a new account, add it to our list */ - if (new) + if (new_acct) purple_accounts_add(account); else purple_signal_emit(pidgin_account_get_handle(), "account-modified", account); @@ -1397,7 +1403,7 @@ /* If this is a new account, then sign on! */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->register_button))) { purple_account_register(account); - } else if (new) { + } else if (new_acct) { const PurpleSavedStatus *saved_status; saved_status = purple_savedstatus_get_current(); @@ -1410,7 +1416,6 @@ /* We no longer need the data from the dialog window */ account_win_destroy_cb(NULL, NULL, dialog); - return account; } static const GtkTargetEntry dnd_targets[] = { @@ -2402,13 +2407,6 @@ g_free(accounts_window); accounts_window = NULL; - - /* See if we're the main window here. */ - if (PIDGIN_BLIST(purple_get_blist())->window == NULL && - purple_connections_get_all() == NULL) { - - purple_core_quit(); - } } static void diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkblist.c --- a/pidgin/gtkblist.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkblist.c Wed Sep 05 22:32:14 2007 +0000 @@ -42,6 +42,7 @@ #include "gtkaccount.h" #include "gtkblist.h" #include "gtkcellrendererexpander.h" +#include "gtkcertmgr.h" #include "gtkconv.h" #include "gtkdebug.h" #include "gtkdialogs.h" @@ -124,7 +125,7 @@ static PidginBuddyList *gtkblist = NULL; static gboolean pidgin_blist_refresh_timer(PurpleBuddyList *list); -static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange); +static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change); static void pidgin_blist_selection_changed(GtkTreeSelection *selection, gpointer data); static void pidgin_blist_update(PurpleBuddyList *list, PurpleBlistNode *node); static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node); @@ -331,8 +332,10 @@ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name, chat->account); - if (conv != NULL) + if (conv != NULL) { + pidgin_conv_attach_to_conversation(conv); purple_conversation_present(conv); + } serv_join_chat(chat->account->gc, chat->components); g_free(chat_name); @@ -611,6 +614,8 @@ static void pidgin_blist_update_privacy_cb(PurpleBuddy *buddy) { + if (buddy->node.ui_data == NULL || ((struct _pidgin_blist_node*)buddy->node.ui_data)->row == NULL) + return; pidgin_blist_update_buddy(purple_get_blist(), (PurpleBlistNode*)(buddy), TRUE); } @@ -1538,6 +1543,12 @@ pidgin_clear_cursor(gtkblist->window); } +static void pidgin_blist_show_protocol_icons_cb(gpointer data, guint action, GtkWidget *item) +{ + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", + gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item))); +} + static void pidgin_blist_show_empty_groups_cb(gpointer data, guint action, GtkWidget *item) { pidgin_set_cursor(gtkblist->window, GDK_WATCH); @@ -2611,6 +2622,7 @@ struct _pidgin_blist_node *gtknode; GdkRectangle mon_size; int sig; + const char *name; if (node == NULL) return; @@ -2668,7 +2680,9 @@ gtknode = node->ui_data; + name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget))); gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE); + gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List")); gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE); gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips"); g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event", @@ -2855,10 +2869,12 @@ { N_("/Buddies/Get User _Info..."), "I", pidgin_dialogs_info, 0, "", PIDGIN_STOCK_TOOLBAR_USER_INFO }, { N_("/Buddies/View User _Log..."), "L", pidgin_dialogs_log, 0, "", NULL }, { "/Buddies/sep1", NULL, NULL, 0, "", NULL }, - { N_("/Buddies/Show _Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "", NULL }, - { N_("/Buddies/Show _Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "", NULL }, - { N_("/Buddies/Show Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "", NULL }, - { N_("/Buddies/Show Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "", NULL }, + { N_("/Buddies/Show"), NULL, NULL, 0, "", NULL}, + { N_("/Buddies/Show/_Offline Buddies"), NULL, pidgin_blist_edit_mode_cb, 1, "", NULL }, + { N_("/Buddies/Show/_Empty Groups"), NULL, pidgin_blist_show_empty_groups_cb, 1, "", NULL }, + { N_("/Buddies/Show/Buddy _Details"), NULL, pidgin_blist_buddy_details_cb, 1, "", NULL }, + { N_("/Buddies/Show/Idle _Times"), NULL, pidgin_blist_show_idle_time_cb, 1, "", NULL }, + { N_("/Buddies/Show/_Protocol Icons"), NULL, pidgin_blist_show_protocol_icons_cb, 1, "", NULL }, { N_("/Buddies/_Sort Buddies"), NULL, NULL, 0, "", NULL }, { "/Buddies/sep2", NULL, NULL, 0, "", NULL }, { N_("/Buddies/_Add Buddy..."), "B", pidgin_blist_add_buddy_cb, 0, "", GTK_STOCK_ADD }, @@ -2874,6 +2890,7 @@ /* Tools */ { N_("/_Tools"), NULL, NULL, 0, "", NULL }, { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 0, "", NULL }, + { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "", NULL }, { N_("/Tools/Plu_gins"), "U", pidgin_plugin_dialog_show, 0, "", PIDGIN_STOCK_TOOLBAR_PLUGINS }, { N_("/Tools/Pr_eferences"), "P", pidgin_prefs_show, 0, "", GTK_STOCK_PREFERENCES }, { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "", NULL }, @@ -3084,7 +3101,8 @@ purple_notify_user_info_add_pair(user_info, _("Status"), _("Offline")); } - if (prpl_info && prpl_info->tooltip_text) + if (purple_account_is_connected(b->account) && + prpl_info && prpl_info->tooltip_text) { /* Additional text from the PRPL */ prpl_info->tooltip_text(b, user_info, full); @@ -3143,10 +3161,11 @@ return ret; } - if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) + if (((struct _pidgin_blist_node*)(node->parent->ui_data))->contact_expanded) { + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons")) + return NULL; return pidgin_create_prpl_icon(((PurpleBuddy*)node)->account, PIDGIN_PRPL_ICON_SMALL); - } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) { - return pidgin_create_prpl_icon(((PurpleChat*)node)->account, PIDGIN_PRPL_ICON_SMALL); + } } else { return NULL; } @@ -3194,6 +3213,7 @@ { GdkPixbuf *ret; const char *protoname = NULL; + const char *icon = NULL; struct _pidgin_blist_node *gtknode = node->ui_data; struct _pidgin_blist_node *gtkbuddynode = NULL; PurpleBuddy *buddy = NULL; @@ -3242,62 +3262,54 @@ purple_buddy_get_name(buddy), purple_buddy_get_account(buddy)); PurplePresence *p; + gboolean trans; + if(conv != NULL) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv) && size == PIDGIN_STATUS_ICON_SMALL) { + if((gtkconv == NULL || pidgin_conv_is_hidden(gtkconv)) && size == PIDGIN_STATUS_ICON_SMALL) { return gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_MESSAGE, icon_size, "GtkTreeView"); } } + p = purple_buddy_get_presence(buddy); + trans = (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL); if (PURPLE_BUDDY_IS_ONLINE(buddy) && gtkbuddynode && gtkbuddynode->recent_signonoff) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGIN, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_LOGIN; else if (gtkbuddynode && gtkbuddynode->recent_signonoff) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_LOGOUT, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_LOGOUT; else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_UNAVAILABLE)) - if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY_I, - icon_size, "GtkTreeView"); + if (trans) + icon = PIDGIN_STOCK_STATUS_BUSY_I; else - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_BUSY, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_BUSY; else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AWAY)) - if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY_I, - icon_size, "GtkTreeView"); + if (trans) + icon = PIDGIN_STOCK_STATUS_AWAY_I; else - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AWAY, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_AWAY; else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_EXTENDED_AWAY)) - if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA_I, - icon_size, "GtkTreeView"); + if (trans) + icon = PIDGIN_STOCK_STATUS_XA_I; else - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_XA, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_XA; else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_OFFLINE, - icon_size, "GtkTreeView"); - else if (purple_presence_is_idle(p) && size == PIDGIN_STATUS_ICON_SMALL) - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE_I, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_OFFLINE; + else if (trans) + icon = PIDGIN_STOCK_STATUS_AVAILABLE_I; else if (purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE)) - ret = gtk_widget_render_icon(GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_INVISIBLE, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_INVISIBLE; else - ret = gtk_widget_render_icon(GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_AVAILABLE, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_AVAILABLE; } else if (chat) { - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_CHAT, - icon_size, "GtkTreeView"); + icon = PIDGIN_STOCK_STATUS_CHAT; } else { - ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), PIDGIN_STOCK_STATUS_PERSON, - icon_size, "GtkTreeView"); - } - + icon = PIDGIN_STOCK_STATUS_PERSON; + } + + ret = gtk_widget_render_icon (GTK_WIDGET(gtkblist->treeview), icon, + icon_size, "GtkTreeView"); return ret; } @@ -3320,7 +3332,7 @@ if(conv != NULL) { gtkconv = PIDGIN_CONVERSATION(conv); - if(gtkconv != NULL && pidgin_conv_is_hidden(gtkconv)) { + if(gtkconv == NULL || pidgin_conv_is_hidden(gtkconv)) { hidden_conv = TRUE; } } @@ -4405,7 +4417,10 @@ G_TYPE_BOOLEAN, /* Contact expander */ G_TYPE_BOOLEAN, /* Contact expander visible */ GDK_TYPE_PIXBUF, /* Emblem */ - G_TYPE_BOOLEAN); /* Emblem visible */ + G_TYPE_BOOLEAN, /* Emblem visible */ + GDK_TYPE_PIXBUF, /* Protocol icon */ + G_TYPE_BOOLEAN /* Protocol visible */ + ); gtkblist->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(gtkblist->treemodel)); @@ -4520,6 +4535,17 @@ "visible", EMBLEM_VISIBLE_COLUMN, NULL); rend = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(column, rend, FALSE); + gtk_tree_view_column_set_attributes(column, rend, + "pixbuf", PROTOCOL_ICON_COLUMN, + "visible", PROTOCOL_ICON_VISIBLE_COLUMN, +#if GTK_CHECK_VERSION(2,6,0) + "cell-background-gdk", BGCOLOR_COLUMN, +#endif + NULL); + g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL); + + rend = gtk_cell_renderer_pixbuf_new(); g_object_set(rend, "xalign", 1.0, "ypad", 0, NULL); gtk_tree_view_column_pack_start(column, rend, FALSE); gtk_tree_view_column_set_attributes(column, rend, "pixbuf", BUDDY_ICON_COLUMN, @@ -4565,21 +4591,24 @@ /* set the Show Offline Buddies option. must be done * after the treeview or faceprint gets mad. -Robot101 */ - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Offline Buddies"))), + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Offline Buddies"))), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies")); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Empty Groups"))), + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Empty Groups"))), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups")); gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Tools/Mute Sounds"))), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute")); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Buddy Details"))), + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Buddy Details"))), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons")); - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show Idle Times"))), + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Idle Times"))), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time")); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_item (gtkblist->ift, N_("/Buddies/Show/Protocol Icons"))), + purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons")); + if(!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none")) gtk_widget_set_sensitive(gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Mute Sounds")), FALSE); @@ -4609,6 +4638,8 @@ _prefs_change_redo_list, NULL); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", _prefs_change_redo_list, NULL); + purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", + _prefs_change_redo_list, NULL); /* sorting */ purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/blist/sort_type", @@ -5040,6 +5071,8 @@ BUDDY_ICON_VISIBLE_COLUMN, biglist, EMBLEM_COLUMN, emblem, EMBLEM_VISIBLE_COLUMN, emblem, + PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(buddy->account, PIDGIN_PRPL_ICON_SMALL), + PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"), BGCOLOR_COLUMN, NULL, CONTACT_EXPANDER_COLUMN, NULL, CONTACT_EXPANDER_VISIBLE_COLUMN, expanded, @@ -5124,7 +5157,7 @@ -static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean statusChange) +static void pidgin_blist_update_buddy(PurpleBuddyList *list, PurpleBlistNode *node, gboolean status_change) { PurpleBuddy *buddy; struct _pidgin_blist_node *gtkparentnode; @@ -5177,6 +5210,10 @@ GdkPixbuf *emblem; char *mark; gboolean showicons = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"); + const char *name = purple_chat_get_name(chat); + PurpleConversation *conv = + purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name, chat->account); + gboolean hidden = (conv && !PIDGIN_CONVERSATION(conv)); if(!insert_node(list, node, &iter)) return; @@ -5192,14 +5229,21 @@ avatar = NULL; mark = g_markup_escape_text(purple_chat_get_name(chat), -1); + if (hidden) { + char *bold = g_strdup_printf("%s", mark); + g_free(mark); + mark = bold; + } gtk_tree_store_set(gtkblist->treemodel, &iter, STATUS_ICON_COLUMN, status, STATUS_ICON_VISIBLE_COLUMN, TRUE, BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar, BUDDY_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"), - EMBLEM_COLUMN, emblem, + EMBLEM_COLUMN, emblem, EMBLEM_VISIBLE_COLUMN, emblem != NULL, + PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(chat->account, PIDGIN_PRPL_ICON_SMALL), + PROTOCOL_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons"), NAME_COLUMN, mark, GROUP_EXPANDER_VISIBLE_COLUMN, FALSE, -1); @@ -6093,6 +6137,7 @@ purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_empty_groups", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time", TRUE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_offline_buddies", FALSE); + purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE); purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE); purple_prefs_add_string(PIDGIN_PREFS_ROOT "/blist/sort_type", "alphabetical"); diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkblist.h --- a/pidgin/gtkblist.h Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkblist.h Wed Sep 05 22:32:14 2007 +0000 @@ -43,6 +43,8 @@ CONTACT_EXPANDER_VISIBLE_COLUMN, EMBLEM_COLUMN, EMBLEM_VISIBLE_COLUMN, + PROTOCOL_ICON_COLUMN, + PROTOCOL_ICON_VISIBLE_COLUMN, BLIST_COLUMNS }; diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkcellrendererexpander.c diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkcertmgr.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkcertmgr.c Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,682 @@ +/* + * @file gtkcertmgr.c GTK+ Certificate Manager API + * @ingroup pidgin + * + * pidgin + * + * Pidgin 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include + +#include "core.h" +#include "internal.h" +#include "pidgin.h" +#include "pidginstock.h" + +#include "certificate.h" +#include "debug.h" +#include "notify.h" +#include "request.h" + +#include "gtkblist.h" +#include "gtkutils.h" + +#include "gtkcertmgr.h" + +/***************************************************************************** + * X.509 tls_peers management interface * + *****************************************************************************/ + +typedef struct { + GtkWidget *mgmt_widget; + GtkTreeView *listview; + GtkTreeSelection *listselect; + GtkWidget *importbutton; + GtkWidget *exportbutton; + GtkWidget *infobutton; + GtkWidget *deletebutton; + PurpleCertificatePool *tls_peers; +} tls_peers_mgmt_data; + +tls_peers_mgmt_data *tpm_dat = NULL; + +/* Columns + See http://developer.gnome.org/doc/API/2.0/gtk/TreeWidget.html */ +enum +{ + TPM_HOSTNAME_COLUMN, + TPM_N_COLUMNS +}; + +static void +tls_peers_mgmt_destroy(GtkWidget *mgmt_widget, gpointer data) +{ + purple_debug_info("certmgr", + "tls peers self-destructs\n"); + + purple_signals_disconnect_by_handle(tpm_dat); + purple_request_close_with_handle(tpm_dat); + g_free(tpm_dat); tpm_dat = NULL; +} + +static void +tls_peers_mgmt_repopulate_list(void) +{ + GtkTreeView *listview = tpm_dat->listview; + PurpleCertificatePool *tls_peers; + GList *idlist, *l; + + GtkListStore *store = GTK_LIST_STORE( + gtk_tree_view_get_model(GTK_TREE_VIEW(listview))); + + /* First, delete everything in the list */ + gtk_list_store_clear(store); + + /* Locate the "tls_peers" pool */ + tls_peers = purple_certificate_find_pool("x509", "tls_peers"); + g_return_if_fail(tls_peers); + + /* Grab the loaded certificates */ + idlist = purple_certificate_pool_get_idlist(tls_peers); + + /* Populate the listview */ + for (l = idlist; l; l = l->next) { + GtkTreeIter iter; + gtk_list_store_append(store, &iter); + + gtk_list_store_set(GTK_LIST_STORE(store), &iter, + TPM_HOSTNAME_COLUMN, l->data, + -1); + } + purple_certificate_pool_destroy_idlist(idlist); +} + +static void +tls_peers_mgmt_mod_cb(PurpleCertificatePool *pool, const gchar *id, gpointer data) +{ + g_assert (pool == tpm_dat->tls_peers); + + tls_peers_mgmt_repopulate_list(); +} + +static void +tls_peers_mgmt_select_chg_cb(GtkTreeSelection *ignored, gpointer data) +{ + GtkTreeSelection *select = tpm_dat->listselect; + GtkTreeIter iter; + GtkTreeModel *model; + + /* See if things are selected */ + if (gtk_tree_selection_get_selected(select, &model, &iter)) { + /* Enable buttons if something is selected */ + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), TRUE); + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), TRUE); + } else { + /* Otherwise, disable them */ + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->exportbutton), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->infobutton), FALSE); + gtk_widget_set_sensitive(GTK_WIDGET(tpm_dat->deletebutton), FALSE); + + } +} + +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? */ + + /* Drop the certificate into the pool */ + purple_certificate_pool_store(tpm_dat->tls_peers, id, crt); + + /* And this certificate is not needed any more */ + 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; + + /* Load the scheme of our tls_peers pool (ought to be x509) */ + x509 = purple_certificate_pool_get_scheme(tpm_dat->tls_peers); + + /* Now load the certificate from disk */ + crt = purple_certificate_import(x509, filename); + + /* Did it work? */ + if (crt != NULL) { + gchar *default_hostname; + /* Get name to add to pool as */ + /* Make a guess about what the hostname should be */ + default_hostname = purple_certificate_get_subject_name(crt); + /* TODO: Find a way to make sure that crt gets destroyed + if the window gets closed unusually, such as by handle + deletion */ + /* TODO: Display some more information on the certificate? */ + purple_request_input(tpm_dat, + _("Certificate Import"), + _("Specify a hostname"), + _("Type the host name this certificate is for."), + default_hostname, + FALSE, /* Not multiline */ + FALSE, /* Not masked? */ + NULL, /* No hints? */ + _("OK"), + G_CALLBACK(tls_peers_mgmt_import_ok2_cb), + _("Cancel"), + G_CALLBACK(tls_peers_mgmt_import_cancel2_cb), + NULL, NULL, NULL, /* No account/who/conv*/ + crt /* Pass cert instance to callback*/ + ); + + g_free(default_hostname); + } else { + /* Errors! Oh no! */ + /* TODO: Perhaps find a way to be specific about what just + went wrong? */ + 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 +tls_peers_mgmt_import_cb(GtkWidget *button, gpointer data) +{ + /* TODO: need to tell the user that we want a .PEM file! */ + purple_request_file(tpm_dat, + _("Select a PEM certificate"), + "certificate.pem", + FALSE, /* Not a save dialog */ + G_CALLBACK(tls_peers_mgmt_import_ok_cb), + NULL, /* Do nothing if cancelled */ + NULL, NULL, NULL, NULL );/* No account,conv,etc. */ +} + +static void +tls_peers_mgmt_export_ok_cb(gpointer data, const char *filename) +{ + PurpleCertificate *crt = (PurpleCertificate *) data; + + g_assert(filename); + + if (!purple_certificate_export(filename, crt)) { + /* Errors! Oh no! */ + /* TODO: Perhaps find a way to be specific about what just + went wrong? */ + 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 +tls_peers_mgmt_export_cancel_cb(gpointer data, const char *filename) +{ + PurpleCertificate *crt = (PurpleCertificate *) data; + /* Pressing cancel just frees the duplicated certificate */ + purple_certificate_destroy(crt); +} + +static void +tls_peers_mgmt_export_cb(GtkWidget *button, gpointer data) +{ + PurpleCertificate *crt; + GtkTreeSelection *select = tpm_dat->listselect; + GtkTreeIter iter; + GtkTreeModel *model; + gchar *id; + + /* See if things are selected */ + if (!gtk_tree_selection_get_selected(select, &model, &iter)) { + purple_debug_warning("gtkcertmgr/tls_peers_mgmt", + "Export clicked with no selection?\n"); + return; + } + + /* Retrieve the selected hostname */ + gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1); + + /* Extract the certificate from the pool now to make sure it doesn't + get deleted out from under us */ + crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id); + + if (NULL == crt) { + purple_debug_error("gtkcertmgr/tls_peers_mgmt", + "Id %s was not in the peers cache?!\n", + id); + g_free(id); + return; + } + g_free(id); + + + /* TODO: inform user that it will be a PEM? */ + purple_request_file(tpm_dat, + _("PEM X.509 Certificate Export"), + "certificate.pem", + TRUE, /* Is a save dialog */ + G_CALLBACK(tls_peers_mgmt_export_ok_cb), + G_CALLBACK(tls_peers_mgmt_export_cancel_cb), + NULL, NULL, NULL, /* No account,conv,etc. */ + crt); /* Pass the certificate on to the callback */ +} + +static void +tls_peers_mgmt_info_cb(GtkWidget *button, gpointer data) +{ + GtkTreeSelection *select = tpm_dat->listselect; + GtkTreeIter iter; + GtkTreeModel *model; + gchar *id; + PurpleCertificate *crt; + gchar *subject; + GByteArray *fpr_sha1; + gchar *fpr_sha1_asc; + gchar *primary, *secondary; + + /* See if things are selected */ + if (!gtk_tree_selection_get_selected(select, &model, &iter)) { + purple_debug_warning("gtkcertmgr/tls_peers_mgmt", + "Info clicked with no selection?\n"); + return; + } + + /* Retrieve the selected hostname */ + gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1); + + /* Now retrieve the certificate */ + crt = purple_certificate_pool_retrieve(tpm_dat->tls_peers, id); + g_return_if_fail(crt); + + /* Build a notification thing */ + /* TODO: This needs a better GUI, but a notification will do for now */ + primary = g_strdup_printf(_("Certificate for %s"), id); + + 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(tpm_dat, + _("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); + g_free(id); + purple_certificate_destroy(crt); +} + +static void +tls_peers_mgmt_delete_confirm_cb(gchar *id, gint choice) +{ + if (1 == choice) { + /* Yes, delete was confirmed */ + /* Now delete the thing */ + if (!purple_certificate_pool_delete(tpm_dat->tls_peers, id)) { + purple_debug_warning("gtkcertmgr/tls_peers_mgmt", + "Deletion failed on id %s\n", + id); + }; + } + + g_free(id); +} + +static void +tls_peers_mgmt_delete_cb(GtkWidget *button, gpointer data) +{ + GtkTreeSelection *select = tpm_dat->listselect; + GtkTreeIter iter; + GtkTreeModel *model; + + /* See if things are selected */ + if (gtk_tree_selection_get_selected(select, &model, &iter)) { + + gchar *id; + gchar *primary; + + /* Retrieve the selected hostname */ + gtk_tree_model_get(model, &iter, TPM_HOSTNAME_COLUMN, &id, -1); + + /* Prompt to confirm deletion */ + primary = g_strdup_printf( + _("Really delete certificate for %s?"), id ); + + purple_request_yes_no(tpm_dat, _("Confirm certificate delete"), + primary, NULL, /* Can this be NULL? */ + 2, /* NO is default action */ + NULL, NULL, NULL, + id, /* id ownership passed to callback */ + tls_peers_mgmt_delete_confirm_cb, + tls_peers_mgmt_delete_confirm_cb ); + + g_free(primary); + + } else { + purple_debug_warning("gtkcertmgr/tls_peers_mgmt", + "Delete clicked with no selection?\n"); + return; + } +} + +static GtkWidget * +tls_peers_mgmt_build(void) +{ + GtkWidget *bbox; + GtkListStore *store; + + /* This block of variables will end up in tpm_dat */ + GtkTreeView *listview; + GtkTreeSelection *select; + GtkWidget *importbutton; + GtkWidget *exportbutton; + GtkWidget *infobutton; + GtkWidget *deletebutton; + /** Element to return to the Certmgr window to put in the Notebook */ + GtkWidget *mgmt_widget; + + /* Create a struct to store context information about this window */ + tpm_dat = g_new0(tls_peers_mgmt_data, 1); + + tpm_dat->mgmt_widget = mgmt_widget = + gtk_hbox_new(FALSE, /* Non-homogeneous */ + PIDGIN_HIG_BORDER); + gtk_widget_show(mgmt_widget); + + /* Ensure that everything gets cleaned up when the dialog box + is closed */ + g_signal_connect(G_OBJECT(mgmt_widget), "destroy", + G_CALLBACK(tls_peers_mgmt_destroy), NULL); + + /* List view */ + store = gtk_list_store_new(TPM_N_COLUMNS, G_TYPE_STRING); + + tpm_dat->listview = listview = + GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(store))); + + { + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + /* Set up the display columns */ + renderer = gtk_cell_renderer_text_new(); + column = gtk_tree_view_column_new_with_attributes( + "Hostname", + renderer, + "text", TPM_HOSTNAME_COLUMN, + NULL); + gtk_tree_view_append_column(GTK_TREE_VIEW(listview), column); + + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), + TPM_HOSTNAME_COLUMN, GTK_SORT_ASCENDING); + } + + /* Get the treeview selector into the struct */ + tpm_dat->listselect = select = + gtk_tree_view_get_selection(GTK_TREE_VIEW(listview)); + + /* Force the selection mode */ + gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); + + /* Use a callback to enable/disable the buttons based on whether + something is selected */ + g_signal_connect(G_OBJECT(select), "changed", + G_CALLBACK(tls_peers_mgmt_select_chg_cb), NULL); + + gtk_box_pack_start(GTK_BOX(mgmt_widget), GTK_WIDGET(listview), + TRUE, TRUE, /* Take up lots of space */ + 0); /* TODO: this padding is wrong */ + gtk_widget_show(GTK_WIDGET(listview)); + + /* Fill the list for the first time */ + tls_peers_mgmt_repopulate_list(); + + /* Right-hand side controls box */ + bbox = gtk_vbutton_box_new(); + gtk_box_pack_end(GTK_BOX(mgmt_widget), bbox, + FALSE, FALSE, /* Do not take up space */ + 0); /* TODO: this padding is probably wrong */ + gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START); + gtk_widget_show(bbox); + + /* Import button */ + /* TODO: This is the wrong stock button */ + tpm_dat->importbutton = importbutton = + gtk_button_new_from_stock(GTK_STOCK_ADD); + gtk_box_pack_start(GTK_BOX(bbox), importbutton, FALSE, FALSE, 0); + gtk_widget_show(importbutton); + g_signal_connect(G_OBJECT(importbutton), "clicked", + G_CALLBACK(tls_peers_mgmt_import_cb), NULL); + + + /* Export button */ + /* TODO: This is the wrong stock button */ + tpm_dat->exportbutton = exportbutton = + gtk_button_new_from_stock(GTK_STOCK_SAVE); + gtk_box_pack_start(GTK_BOX(bbox), exportbutton, FALSE, FALSE, 0); + gtk_widget_show(exportbutton); + g_signal_connect(G_OBJECT(exportbutton), "clicked", + G_CALLBACK(tls_peers_mgmt_export_cb), NULL); + + + /* Info button */ + tpm_dat->infobutton = infobutton = + gtk_button_new_from_stock(PIDGIN_STOCK_INFO); + gtk_box_pack_start(GTK_BOX(bbox), infobutton, FALSE, FALSE, 0); + gtk_widget_show(infobutton); + g_signal_connect(G_OBJECT(infobutton), "clicked", + G_CALLBACK(tls_peers_mgmt_info_cb), NULL); + + + /* Delete button */ + tpm_dat->deletebutton = deletebutton = + gtk_button_new_from_stock(GTK_STOCK_DELETE); + gtk_box_pack_start(GTK_BOX(bbox), deletebutton, FALSE, FALSE, 0); + gtk_widget_show(deletebutton); + g_signal_connect(G_OBJECT(deletebutton), "clicked", + G_CALLBACK(tls_peers_mgmt_delete_cb), NULL); + + /* Call the "selection changed" callback, which will probably disable + all the buttons since nothing is selected yet */ + tls_peers_mgmt_select_chg_cb(select, NULL); + + /* Bind us to the tls_peers pool */ + tpm_dat->tls_peers = purple_certificate_find_pool("x509", "tls_peers"); + + /**** libpurple signals ****/ + /* Respond to certificate add/remove by just reloading everything */ + purple_signal_connect(tpm_dat->tls_peers, "certificate-stored", + tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb), + NULL); + purple_signal_connect(tpm_dat->tls_peers, "certificate-deleted", + tpm_dat, PURPLE_CALLBACK(tls_peers_mgmt_mod_cb), + NULL); + + return mgmt_widget; +} + +PidginCertificateManager tls_peers_mgmt = { + tls_peers_mgmt_build, /* Widget creation function */ + N_("SSL Servers") +}; + +/***************************************************************************** + * GTK+ main certificate manager * + *****************************************************************************/ +typedef struct +{ + GtkWidget *window; + GtkWidget *notebook; + + GtkWidget *closebutton; +} CertMgrDialog; + +/* If a certificate manager window is open, this will point to it. + So if it is set, don't open another one! */ +CertMgrDialog *certmgr_dialog = NULL; + +static void +certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg) +{ + /* TODO: Ignoring the arguments to this function may not be ideal, + but there *should* only be "one dialog to rule them all" at a time*/ + pidgin_certmgr_hide(); +} + +void +pidgin_certmgr_show(void) +{ + CertMgrDialog *dlg; + GtkWidget *win; + GtkWidget *vbox; + GtkWidget *bbox; + + /* Enumerate all the certificates on file */ + { + GList *idlist, *poollist; + + for ( poollist = purple_certificate_get_pools(); + poollist; + poollist = poollist->next ) { + PurpleCertificatePool *pool = poollist->data; + GList *l; + + purple_debug_info("gtkcertmgr", + "Pool %s found for scheme %s -" + "Enumerating certificates:\n", + pool->name, pool->scheme_name); + + idlist = purple_certificate_pool_get_idlist(pool); + + for (l=idlist; l; l = l->next) { + purple_debug_info("gtkcertmgr", + "- %s\n", + l->data ? (gchar *) l->data : "(null)"); + } /* idlist */ + purple_certificate_pool_destroy_idlist(idlist); + } /* poollist */ + } + + + /* If the manager is already open, bring it to the front */ + if (certmgr_dialog != NULL) { + gtk_window_present(GTK_WINDOW(certmgr_dialog->window)); + return; + } + + /* Create the dialog, and set certmgr_dialog so we never create + more than one at a time */ + dlg = certmgr_dialog = g_new0(CertMgrDialog, 1); + + win = dlg->window = + pidgin_create_window(_("Certificate Manager"),/* Title */ + PIDGIN_HIG_BORDER, /*Window border*/ + "certmgr", /* Role */ + TRUE); /* Allow resizing */ + g_signal_connect(G_OBJECT(win), "delete_event", + G_CALLBACK(certmgr_close_cb), dlg); + + + /* TODO: Retrieve the user-set window size and use it */ + gtk_window_set_default_size(GTK_WINDOW(win), 400, 400); + + /* Main vbox */ + vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER ); + gtk_container_add(GTK_CONTAINER(win), vbox); + gtk_widget_show(vbox); + + /* Notebook of various certificate managers */ + dlg->notebook = gtk_notebook_new(); + gtk_box_pack_start(GTK_BOX(vbox), dlg->notebook, + TRUE, TRUE, /* Notebook should take extra space */ + 0); + gtk_widget_show(dlg->notebook); + + /* Box for the close button */ + bbox = gtk_hbutton_box_new(); + gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); + gtk_widget_show(bbox); + + /* Close button */ + dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE); + gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0); + gtk_widget_show(dlg->closebutton); + g_signal_connect(G_OBJECT(dlg->closebutton), "clicked", + G_CALLBACK(certmgr_close_cb), dlg); + + /* Add the defined certificate managers */ + /* TODO: Find a way of determining whether each is shown or not */ + /* TODO: Implement this correctly */ + gtk_notebook_append_page(GTK_NOTEBOOK (dlg->notebook), + (tls_peers_mgmt.build)(), + gtk_label_new(_(tls_peers_mgmt.label)) ); + + gtk_widget_show(win); +} + +void +pidgin_certmgr_hide(void) +{ + /* If it isn't open, do nothing */ + if (certmgr_dialog == NULL) { + return; + } + + purple_signals_disconnect_by_handle(certmgr_dialog); + purple_prefs_disconnect_by_handle(certmgr_dialog); + + gtk_widget_destroy(certmgr_dialog->window); + g_free(certmgr_dialog); + certmgr_dialog = NULL; +} diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkcertmgr.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkcertmgr.h Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,62 @@ +/** + * @file gtkcertmgr.h GTK+ Certificate Manager API + * @ingroup pidgin + */ +/* + * pidgin + * + * Pidgin 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _PIDGINCERTMGR_H_ +#define _PIDGINCERTMGR_H_ + +/************************************************************************** + * @name Structures * + **************************************************************************/ +typedef struct _PidginCertificateManager PidginCertificateManager; + +/** + * GTK+ Certificate Manager subwidget + */ +struct _PidginCertificateManager { + /** Create, configure, show, and return the management interface */ + GtkWidget * (* build)(void); + /** Notebook label to use in the CertMgr dialog */ + gchar *label; +}; + +/**************************************************************************/ +/** @name Certificate Manager API */ +/**************************************************************************/ +/*@{*/ +/** + * Show the certificate manager window + */ +void pidgin_certmgr_show(void); + +/** + * Hide the certificate manager window + */ +void pidgin_certmgr_hide(void); + +/*@}*/ + +#endif /* _PIDGINCERTMGR_H_ */ diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkconv.c --- a/pidgin/gtkconv.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkconv.c Wed Sep 05 22:32:14 2007 +0000 @@ -86,6 +86,7 @@ CONV_ICON_COLUMN, CONV_TEXT_COLUMN, CONV_EMBLEM_COLUMN, + CONV_PROTOCOL_ICON_COLUMN, CONV_NUM_COLUMNS } PidginInfopaneColumns; @@ -138,6 +139,7 @@ static void generate_send_to_items(PidginWindow *win); /* Prototypes. <-- because Paco-Paco hates this comment. */ +static gboolean infopane_entry_activate(PidginConversation *gtkconv); static void got_typing_keypress(PidginConversation *gtkconv, gboolean first); static void gray_stuff_out(PidginConversation *gtkconv); static GList *generate_invite_user_names(PurpleConnection *gc); @@ -156,7 +158,8 @@ static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win); static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv); static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv); -static gboolean alias_double_click_cb(GtkWidget *widget, GdkEventButton *event, PidginConversation *gtkconv); +static gboolean pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv); +static void pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv); static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y, int width, int height); @@ -325,6 +328,7 @@ const char *cmd, char **args, char **error, void *data) { clear_conversation_scrollback(conv); + purple_conversation_clear_message_history(conv); return PURPLE_CMD_STATUS_OK; } @@ -1298,6 +1302,17 @@ } static void +menu_hide_conv_cb(gpointer data, guint action, GtkWidget *widget) +{ + PidginWindow *win = data; + PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); + PurpleConversation *conv = pidgin_conv_window_get_active_conversation(win); + purple_signal_emit(pidgin_conversations_get_handle(), + "conversation-hiding", gtkconv); + purple_conversation_set_ui_ops(conv, NULL); +} + +static void menu_close_conv_cb(gpointer data, guint action, GtkWidget *widget) { PidginWindow *win = data; @@ -1778,17 +1793,108 @@ } static gboolean +conv_keypress_common(PidginConversation *gtkconv, GdkEventKey *event) +{ + PidginWindow *win; + PurpleConversation *conv; + int curconv; + + conv = gtkconv->active_conv; + win = gtkconv->win; + curconv = gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook)); + + /* If CTRL was held down... */ + if (event->state & GDK_CONTROL_MASK) { + switch (event->keyval) { + case GDK_Page_Down: + case ']': + if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv + 1)) + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); + else + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv + 1); + return TRUE; + break; + + case GDK_Page_Up: + case '[': + if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv - 1)) + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), -1); + else + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv - 1); + return TRUE; + break; + + case GDK_Tab: + case GDK_ISO_Left_Tab: + if (event->state & GDK_SHIFT_MASK) { + move_to_next_unread_tab(gtkconv, FALSE); + } else { + move_to_next_unread_tab(gtkconv, TRUE); + } + + return TRUE; + break; + + case GDK_comma: + gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), + gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), + curconv - 1); + break; + + case GDK_period: + gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), + gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), +#if GTK_CHECK_VERSION(2,2,0) + (curconv + 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win->notebook))); +#else + (curconv + 1) % g_list_length(GTK_NOTEBOOK(win->notebook)->children)); +#endif + break; + + } /* End of switch */ + } + + /* If ALT (or whatever) was held down... */ + else if (event->state & GDK_MOD1_MASK) + { + if (event->keyval > '0' && event->keyval <= '9') + { + guint switchto = event->keyval - '1'; + if (switchto < pidgin_conv_window_get_gtkconv_count(win)) + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), switchto); + + return TRUE; + } + } + + /* If neither CTRL nor ALT were held down... */ + else + { + switch (event->keyval) { + case GDK_F2: + if (gtk_widget_is_focus(GTK_WIDGET(win->notebook))) { + infopane_entry_activate(gtkconv); + return TRUE; + } + break; + } + } + return FALSE; +} + +static gboolean entry_key_press_cb(GtkWidget *entry, GdkEventKey *event, gpointer data) { PidginWindow *win; PurpleConversation *conv; PidginConversation *gtkconv; - int curconv; gtkconv = (PidginConversation *)data; conv = gtkconv->active_conv; win = gtkconv->win; - curconv = gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook)); + + if (conv_keypress_common(gtkconv, event)) + return TRUE; /* If CTRL was held down... */ if (event->state & GDK_CONTROL_MASK) { @@ -1886,88 +1992,32 @@ return TRUE; break; - - case GDK_Page_Down: - case ']': - if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv + 1)) - gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); - else - gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv + 1); - return TRUE; - break; - - case GDK_Page_Up: - case '[': - if (!pidgin_conv_window_get_gtkconv_at_index(win, curconv - 1)) - gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), -1); - else - gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), curconv - 1); - return TRUE; - break; - - case GDK_Tab: - case GDK_ISO_Left_Tab: - if (event->state & GDK_SHIFT_MASK) { - move_to_next_unread_tab(gtkconv, FALSE); - } else { - move_to_next_unread_tab(gtkconv, TRUE); - } - - return TRUE; - break; - - case GDK_comma: - gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), - gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), - curconv - 1); - break; - - case GDK_period: - gtk_notebook_reorder_child(GTK_NOTEBOOK(win->notebook), - gtk_notebook_get_nth_page(GTK_NOTEBOOK(win->notebook), curconv), -#if GTK_CHECK_VERSION(2,2,0) - (curconv + 1) % gtk_notebook_get_n_pages(GTK_NOTEBOOK(win->notebook))); -#else - (curconv + 1) % g_list_length(GTK_NOTEBOOK(win->notebook)->children)); -#endif - break; - } /* End of switch */ } /* If ALT (or whatever) was held down... */ - else if (event->state & GDK_MOD1_MASK) - { - if (event->keyval > '0' && event->keyval <= '9') - { - guint switchto = event->keyval - '1'; - if (switchto < pidgin_conv_window_get_gtkconv_count(win)) - gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), switchto); - - return TRUE; - } + else if (event->state & GDK_MOD1_MASK) { + } /* If neither CTRL nor ALT were held down... */ - else - { - switch (event->keyval) - { - case GDK_Tab: - if (gtkconv->entry != entry) - break; - return tab_complete(conv); + else { + switch (event->keyval) { + case GDK_Tab: + if (gtkconv->entry != entry) break; - - case GDK_Page_Up: - gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); - return TRUE; - break; - - case GDK_Page_Down: - gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); - return TRUE; - break; + return tab_complete(conv); + break; + + case GDK_Page_Up: + gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); + return TRUE; + break; + + case GDK_Page_Down: + gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); + return TRUE; + break; } } @@ -2008,6 +2058,7 @@ /* If we have a valid key for the conversation display, then exit */ if ((event->state & GDK_CONTROL_MASK) || + (event->keyval == GDK_F6) || (event->keyval == GDK_F10) || (event->keyval == GDK_Shift_L) || (event->keyval == GDK_Shift_R) || @@ -2018,11 +2069,17 @@ (event->keyval == GDK_Down) || (event->keyval == GDK_Left) || (event->keyval == GDK_Right) || + (event->keyval == GDK_Page_Up) || + (event->keyval == GDK_Page_Down) || (event->keyval == GDK_Home) || (event->keyval == GDK_End) || (event->keyval == GDK_Tab) || (event->keyval == GDK_ISO_Left_Tab)) - return FALSE; + { + if (event->type == GDK_KEY_PRESS) + return conv_keypress_common(gtkconv, event); + return FALSE; + } if (event->type == GDK_KEY_RELEASE) gtk_widget_grab_focus(gtkconv->entry); @@ -2361,6 +2418,12 @@ &(gtkconv->infopane_iter), CONV_EMBLEM_COLUMN, emblem, -1); + if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_protocol_icons")) { + gtk_list_store_set(GTK_LIST_STORE(gtkconv->infopane_model), + &(gtkconv->infopane_iter), + CONV_PROTOCOL_ICON_COLUMN, pidgin_create_prpl_icon(gtkconv->active_conv->account, PIDGIN_PRPL_ICON_SMALL), -1); + } + /* XXX seanegan Why do I have to do this? */ gtk_widget_queue_draw(gtkconv->infopane); @@ -2497,7 +2560,6 @@ gtkconv->u.im->show_icon = FALSE; gtkwin = gtkconv->win; - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtkwin->menu.show_icon), FALSE); } static void @@ -2658,31 +2720,6 @@ return TRUE; } -static void -menu_buddyicon_cb(gpointer data, guint action, GtkWidget *widget) -{ - PidginWindow *win = data; - PurpleConversation *conv; - PidginConversation *gtkconv; - gboolean active; - - conv = pidgin_conv_window_get_active_conversation(win); - - if (!conv) - return; - - g_return_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM); - - gtkconv = PIDGIN_CONVERSATION(conv); - - active = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)); - gtkconv->u.im->show_icon = active; - if (active) - pidgin_conv_update_buddy_icon(conv); - else - remove_icon(NULL, gtkconv); -} - /************************************************************************** * End of the bunch of buddy icon functions **************************************************************************/ @@ -2690,6 +2727,7 @@ pidgin_conv_present_conversation(PurpleConversation *conv) { PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + GdkModifierType state; if(gtkconv->win==hidden_convwin) { pidgin_conv_window_remove_gtkconv(hidden_convwin, gtkconv); @@ -2697,7 +2735,10 @@ } pidgin_conv_switch_active_conversation(conv); - pidgin_conv_window_switch_gtkconv(gtkconv->win, gtkconv); + /* Switch the tab only if the user initiated the event by pressing + * a button or hitting a key. */ + if (gtk_get_current_event_state(&state)) + pidgin_conv_window_switch_gtkconv(gtkconv->win, gtkconv); gtk_window_present(GTK_WINDOW(gtkconv->win->window)); } @@ -2723,7 +2764,7 @@ PurpleConversation *conv = (PurpleConversation*)l->data; PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); - if(gtkconv->active_conv != conv) + if(gtkconv == NULL || gtkconv->active_conv != conv) continue; if (gtkconv->unseen_state >= min_state @@ -2745,6 +2786,18 @@ pidgin_conv_present_conversation(conv); } +static void +unseen_all_conv_menu_cb(GtkMenuItem *item, GList *list) +{ + g_return_if_fail(list != NULL); + /* Do not free the list from here. It will be freed from the + * 'destroy' callback on the menuitem. */ + while (list) { + pidgin_conv_present_conversation(list->data); + list = list->next; + } +} + guint pidgin_conversations_fill_menu(GtkWidget *menu, GList *convs) { @@ -2776,6 +2829,19 @@ ret++; } + if (convs->next) { + /* There are more than one conversation. Add an option to show all conversations. */ + GtkWidget *item; + GList *list = g_list_copy(convs); + + pidgin_separator(menu); + + item = gtk_menu_item_new_with_label(_("Show All")); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(unseen_all_conv_menu_cb), list); + g_signal_connect_swapped(G_OBJECT(item), "destroy", G_CALLBACK(g_list_free), list); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + } + return ret; } @@ -2837,6 +2903,8 @@ { "/Conversation/sep4", NULL, NULL, 0, "", NULL }, + { N_("/Conversation/_Hide"), NULL, menu_hide_conv_cb, 0, + "", NULL}, { N_("/Conversation/_Close"), NULL, menu_close_conv_cb, 0, "", GTK_STOCK_CLOSE }, @@ -2844,7 +2912,6 @@ { N_("/_Options"), NULL, NULL, 0, "", NULL }, { N_("/Options/Enable _Logging"), NULL, menu_logging_cb, 0, "", NULL }, { N_("/Options/Enable _Sounds"), NULL, menu_sounds_cb, 0, "", NULL }, - { N_("/Options/Show Buddy _Icon"), NULL, menu_buddyicon_cb, 0, "", NULL }, { "/Options/sep0", NULL, NULL, 0, "", NULL }, { N_("/Options/Show Formatting _Toolbars"), NULL, menu_toolbar_cb, 0, "", NULL }, { N_("/Options/Show Ti_mestamps"), "F2", menu_timestamps_cb, 0, "", NULL }, @@ -2884,31 +2951,6 @@ } } -static void -show_buddy_icons_pref_changed_cb(const char *name, PurplePrefType type, - gconstpointer value, gpointer data) -{ - PidginWindow *win = data; - gboolean show_icons = GPOINTER_TO_INT(value); - - if (!show_icons) - { - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), - FALSE); - gtk_widget_set_sensitive(win->menu.show_icon, FALSE); - } - else - { - PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); - - if (gtkconv != NULL) - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), - TRUE); - gtk_widget_set_sensitive(win->menu.show_icon, TRUE); - - } -} - /* Returns TRUE if some items were added to the menu, FALSE otherwise */ static gboolean populate_menu_with_options(GtkWidget *menu, PidginConversation *gtkconv, gboolean all) @@ -2967,8 +3009,12 @@ node = (PurpleBlistNode *)buddy; /* Now add the stuff */ - if (all && buddy) { - pidgin_blist_make_buddy_menu(menu, buddy, TRUE); + if (all) { + if (buddy) + pidgin_blist_make_buddy_menu(menu, buddy, TRUE); + else if (chat) { + /* XXX: */ + } } else if (node) { if (purple_account_is_connected(conv->account)) pidgin_append_blist_node_proto_menu(menu, conv->account->gc, node); @@ -3201,17 +3247,7 @@ win->menu.show_timestamps = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Options/Show Timestamps")); - win->menu.show_icon = - gtk_item_factory_get_widget(win->menu.item_factory, - N_("/Options/Show Buddy Icon")); - if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) - { - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), - FALSE); - gtk_widget_set_sensitive(win->menu.show_icon, FALSE); - } - purple_prefs_connect_callback(win, PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", - show_buddy_icons_pref_changed_cb, win); + win->menu.show_icon = NULL; win->menu.tray = pidgin_menu_tray_new(); gtk_menu_shell_append(GTK_MENU_SHELL(win->menu.menubar), @@ -4368,6 +4404,10 @@ g_signal_connect(G_OBJECT(list), "button_press_event", G_CALLBACK(right_click_chat_cb), gtkconv); + g_signal_connect(G_OBJECT(list), "motion-notify-event", + G_CALLBACK(pidgin_userlist_motion_cb), gtkconv); + g_signal_connect(G_OBJECT(list), "leave-notify-event", + G_CALLBACK(pidgin_userlist_motion_cb), gtkconv); g_signal_connect(G_OBJECT(list), "popup-menu", G_CALLBACK(gtkconv_chat_popup_menu_cb), gtkconv); g_signal_connect(G_OBJECT(lbox), "size-allocate", G_CALLBACK(lbox_size_allocate_cb), gtkconv); @@ -4412,6 +4452,8 @@ int timeout; PidginConversation *gtkconv; /* This is the Pidgin conversation that triggered the tooltip */ + int userlistx; + int userlisty; } tooltip; static void @@ -4468,6 +4510,72 @@ return FALSE; } +static gboolean +pidgin_userlist_tooltip_timeout(PidginConversation *gtkconv) +{ + PurplePluginProtocolInfo *prpl_info; + PurpleConversation *conv = gtkconv->active_conv; + PidginChatPane *gtkchat; + PurpleConnection *gc; + PurpleBlistNode *node = NULL; + PurpleAccount *account; + GtkTreePath *path; + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreeViewColumn *column; + gchar *who; + int x, y; + + gtkchat = gtkconv->u.chat; + account = purple_conversation_get_account(conv); + gc = account->gc; + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list)); + + gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(gtkchat->list), + tooltip.userlistx, tooltip.userlisty, &path, &column, &x, &y); + + if (path == NULL) + return FALSE; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path); + gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, CHAT_USERS_NAME_COLUMN, &who, -1); + + node = (PurpleBlistNode*)(purple_find_buddy(conv->account, who)); + if (node && prpl_info && (prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) + pidgin_blist_draw_tooltip(node, gtkconv->infopane); + + g_free(who); + gtk_tree_path_free(path); + + + return FALSE; +} + +static gboolean +pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv) +{ + PurpleConversation *conv; + int delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); + + pidgin_blist_tooltip_destroy(); + if (delay == 0) + return FALSE; + + if (tooltip.timeout != 0) + g_source_remove(tooltip.timeout); + + conv = gtkconv->active_conv; + + tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_userlist_tooltip_timeout, gtkconv); + tooltip.gtkconv = gtkconv; + tooltip.userlistx = event->x; + tooltip.userlisty = event->y; + + return FALSE; +} + static GtkWidget * setup_common_pane(PidginConversation *gtkconv) { @@ -4507,7 +4615,7 @@ G_CALLBACK(pidgin_conv_leave_cb), gtkconv); gtkconv->infopane = gtk_cell_view_new(); - gtkconv->infopane_model = gtk_list_store_new(CONV_NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF); + gtkconv->infopane_model = gtk_list_store_new(CONV_NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF); gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv->infopane), GTK_TREE_MODEL(gtkconv->infopane_model)); gtk_list_store_append(gtkconv->infopane_model, &(gtkconv->infopane_iter)); @@ -4532,6 +4640,12 @@ g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL); #endif + + rend = gtk_cell_renderer_pixbuf_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, FALSE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_PROTOCOL_ICON_COLUMN, NULL); + g_object_set(rend, "xalign", 0.0, "xpad", 3, "ypad", 0, NULL); + rend = gtk_cell_renderer_pixbuf_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, FALSE); gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_EMBLEM_COLUMN, NULL); @@ -4998,6 +5112,10 @@ g_list_foreach(gtkconv->send_history, (GFunc)g_free, NULL); g_list_free(gtkconv->send_history); + if (gtkconv->attach.timer) { + g_source_remove(gtkconv->attach.timer); + } + if (tooltip.gtkconv == gtkconv) reset_tooltip(); @@ -5214,6 +5332,15 @@ gtkconv = PIDGIN_CONVERSATION(conv); g_return_if_fail(gtkconv != NULL); + if (gtkconv->attach.timer) { + /* We are currently in the process of filling up the buffer with the message + * history of the conversation. So we do not need to add the message here. + * Instead, this message will be added to the message-list, which in turn will + * be processed and displayed by the attach-callback. + */ + return; + } + if (conv != gtkconv->active_conv) { if (flags & PURPLE_MESSAGE_ACTIVE_ONLY) @@ -5581,6 +5708,7 @@ account, name, displaying, conv, flags); g_free(displaying); } + static void pidgin_conv_chat_add_users(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals) { @@ -6077,7 +6205,6 @@ gtk_widget_show(win->menu.insert_link); gtk_widget_show(win->menu.insert_image); - gtk_widget_show(win->menu.show_icon); } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { /* Show stuff that applies to Chats, hide stuff that applies to IMs */ @@ -6090,7 +6217,6 @@ gtk_widget_show(win->menu.alias); gtk_widget_hide(win->menu.block); gtk_widget_hide(win->menu.unblock); - gtk_widget_hide(win->menu.show_icon); if ((account == NULL) || purple_blist_find_chat(account, purple_conversation_get_name(conv)) == NULL) { /* If the chat is NOT in the buddy list */ @@ -6857,7 +6983,8 @@ for (l = purple_get_ims(); l != NULL; l = l->next) { conv = (PurpleConversation *)l->data; gtkconv = PIDGIN_CONVERSATION(conv); - gtkconv->u.im->animate = GPOINTER_TO_INT(value); + if (gtkconv) + gtkconv->u.im->animate = GPOINTER_TO_INT(value); } /* Now either stop or start animation for the active conversation in each window */ @@ -6876,6 +7003,8 @@ for (l = purple_get_conversations(); l != NULL; l = l->next) { PurpleConversation *conv = l->data; + if (!PIDGIN_CONVERSATION(conv)) + continue; if (GPOINTER_TO_INT(value)) gtk_widget_show(PIDGIN_CONVERSATION(conv)->infopane_hbox); else @@ -6888,6 +7017,18 @@ } static void +show_protocol_icons_pref_cb(const char *name, PurplePrefType type, + gconstpointer value, gpointer data) +{ + GList *l; + for (l = purple_get_conversations(); l != NULL; l = l->next) { + PurpleConversation *conv = l->data; + if (PIDGIN_CONVERSATION(conv)) + update_tab_icon(conv); + } +} + +static void conv_placement_usetabs_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) { @@ -7137,6 +7278,60 @@ pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC); } +static gboolean +add_message_history_to_gtkconv(gpointer data) +{ + PidginConversation *gtkconv = data; + int count = 0; + int timer = gtkconv->attach.timer; + gtkconv->attach.timer = 0; + while (gtkconv->attach.current && count < 100) { /* XXX: 100 is a random value here */ + PurpleConvMessage *msg = gtkconv->attach.current->data; + pidgin_conv_write_conv(gtkconv->active_conv, msg->who, msg->who, msg->what, msg->flags, msg->when); + gtkconv->attach.current = gtkconv->attach.current->prev; + count++; + } + gtkconv->attach.timer = timer; + if (gtkconv->attach.current) + return TRUE; + + purple_signal_emit(pidgin_conversations_get_handle(), + "conversation-displayed", gtkconv); + g_source_remove(gtkconv->attach.timer); + gtkconv->attach.timer = 0; + return FALSE; +} + +gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv) +{ + GList *list; + PidginConversation *gtkconv; + + if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) + return FALSE; + + purple_conversation_set_ui_ops(conv, pidgin_conversations_get_conv_ui_ops()); + private_gtkconv_new(conv, FALSE); + gtkconv = PIDGIN_CONVERSATION(conv); + + list = purple_conversation_get_message_history(conv); + if (list) { + list = g_list_last(list); + gtkconv->attach.current = list; + gtkconv->attach.timer = g_idle_add(add_message_history_to_gtkconv, gtkconv); + } else { + purple_signal_emit(pidgin_conversations_get_handle(), + "conversation-displayed", gtkconv); + } + + if (conv->type == PURPLE_CONV_TYPE_CHAT) { + pidgin_conv_update_fields(conv, PIDGIN_CONV_TOPIC); + pidgin_conv_chat_add_users(conv, PURPLE_CONV_CHAT(conv)->in_room, TRUE); + } + + return TRUE; +} + void * pidgin_conversations_get_handle(void) { @@ -7228,6 +7423,8 @@ animate_buddy_icons_pref_cb, NULL); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", show_buddy_icons_pref_cb, NULL); + purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/show_protocol_icons", + show_protocol_icons_pref_cb, NULL); purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/conversations/im/hide_new", hide_new_pref_cb, NULL); @@ -7312,6 +7509,16 @@ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONVERSATION)); + purple_signal_register(handle, "conversation-hiding", + purple_marshal_VOID__POINTER_POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_BOXED, + "PidginConversation *")); + + purple_signal_register(handle, "conversation-displayed", + purple_marshal_VOID__POINTER_POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_BOXED, + "PidginConversation *")); + /********************************************************************** * Register commands **********************************************************************/ @@ -7742,7 +7949,7 @@ infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *gtkconv) { if (e->type == GDK_2BUTTON_PRESS && e->button == 1) { - if (alias_double_click_cb(widget, e, gtkconv)) + if (infopane_entry_activate(gtkconv)) return TRUE; } @@ -7983,10 +8190,11 @@ gtk_window_get_size(GTK_WINDOW(dest_win->window), &win_width, &win_height); - +#ifdef WIN32 /* only override window manager placement on Windows */ gtk_window_move(GTK_WINDOW(dest_win->window), e->x_root - (win_width / 2), e->y_root - (win_height / 2)); +#endif pidgin_conv_window_show(dest_win); } @@ -8199,16 +8407,12 @@ } static gboolean -alias_double_click_cb(GtkWidget *widget, GdkEventButton *event, PidginConversation *gtkconv) +infopane_entry_activate(PidginConversation *gtkconv) { GtkWidget *entry = NULL; PurpleConversation *conv = gtkconv->active_conv; const char *text = NULL; - if (event->button != 1 || event->type != GDK_2BUTTON_PRESS) { - return FALSE; - } - if (!GTK_WIDGET_VISIBLE(gtkconv->tab_label)) { /* There's already an entry for alias. Let's not create another one. */ return FALSE; @@ -8251,7 +8455,15 @@ gtk_widget_hide(gtkconv->infopane); gtk_widget_grab_focus(entry); - return FALSE; + return TRUE; +} + +static gboolean +window_keypress_cb(GtkWidget *widget, GdkEventKey *event, PidginWindow *win) +{ + PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win); + + return conv_keypress_common(gtkconv, event); } static void @@ -8296,13 +8508,6 @@ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps")); - if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM && - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons")) - { - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(win->menu.show_icon), - gtkconv->u.im->show_icon); - } - /* * We pause icons when they are not visible. If this icon should * be animated then start it back up again. @@ -8449,6 +8654,11 @@ g_signal_connect(G_OBJECT(win->window), "focus_in_event", G_CALLBACK(focus_win_cb), win); + /* Intercept keystrokes from the menu items */ + g_signal_connect(G_OBJECT(win->window), "key_press_event", + G_CALLBACK(window_keypress_cb), win); + + /* Create the notebook. */ win->notebook = gtk_notebook_new(); @@ -8777,9 +8987,8 @@ if (pidgin_conv_window_get_gtkconv_count(win) == 1) gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs") && - (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons") || - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_LEFT || - purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_RIGHT)); + (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons") || + purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") != GTK_POS_TOP)); /* show the widgets */ /* gtk_widget_show(gtkconv->icon); */ diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkconv.h --- a/pidgin/gtkconv.h Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkconv.h Wed Sep 05 22:32:14 2007 +0000 @@ -162,6 +162,13 @@ GtkWidget *infopane; GtkListStore *infopane_model; GtkTreeIter infopane_iter; + + /* Used when attaching a PidginConversation to a PurpleConversation + * with message history */ + struct { + int timer; + GList *current; + } attach; }; /*@}*/ @@ -238,6 +245,15 @@ */ void pidgin_conv_present_conversation(PurpleConversation *conv); +/** + * Reattach Pidgin UI to a conversation. + * + * @param conv The conversation. + * + * @return Wheter Pidgin UI was successfully attached. + */ +gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv); + PidginWindow *pidgin_conv_get_window(PidginConversation *gtkconv); GdkPixbuf *pidgin_conv_get_tab_icon(PurpleConversation *conv, gboolean small_icon); void pidgin_conv_new(PurpleConversation *conv); diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkdialogs.c Wed Sep 05 22:32:14 2007 +0000 @@ -97,6 +97,9 @@ {"John 'rekkanoryo' Bailey", NULL, NULL}, {"Peter 'Bleeter' Lawler", NULL, NULL}, {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL}, + {"Peter 'Fmoo' Ruibal", NULL, NULL}, + {"Gabriel 'Nix' Schulhof", NULL, NULL}, + {"Will 'resiak' Thompson", NULL, NULL}, {NULL, NULL, NULL} }; @@ -766,6 +769,7 @@ if (conv == NULL) conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, username); + pidgin_conv_attach_to_conversation(conv); purple_conversation_present(conv); } diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkimhtml.c Wed Sep 05 22:32:14 2007 +0000 @@ -4890,7 +4890,7 @@ } else if (c == '"') { str = g_string_append(str, """); } else if (c == '\n') { - str = g_string_append(str, "
"); + str = g_string_append(str, "
\n"); } else { str = g_string_append_unichar(str, c); } diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Wed Sep 05 22:32:14 2007 +0000 @@ -28,6 +28,7 @@ #include "imgstore.h" #include "notify.h" +#include "prefs.h" #include "request.h" #include "pidginstock.h" #include "util.h" @@ -786,6 +787,7 @@ gtk_widget_set_sensitive(GTK_WIDGET(toolbar->bold), buttons & GTK_IMHTML_BOLD); gtk_widget_set_sensitive(GTK_WIDGET(toolbar->italic), buttons & GTK_IMHTML_ITALIC); gtk_widget_set_sensitive(GTK_WIDGET(toolbar->underline), buttons & GTK_IMHTML_UNDERLINE); + gtk_widget_set_sensitive(GTK_WIDGET(toolbar->strikethrough), buttons & GTK_IMHTML_STRIKE); gtk_widget_set_sensitive(GTK_WIDGET(toolbar->larger_size), buttons & GTK_IMHTML_GROW); gtk_widget_set_sensitive(GTK_WIDGET(toolbar->smaller_size), buttons & GTK_IMHTML_SHRINK); @@ -798,6 +800,7 @@ (buttons & GTK_IMHTML_BOLD || buttons & GTK_IMHTML_ITALIC || buttons & GTK_IMHTML_UNDERLINE || + buttons & GTK_IMHTML_STRIKE || buttons & GTK_IMHTML_GROW || buttons & GTK_IMHTML_SHRINK || buttons & GTK_IMHTML_FACE || @@ -831,7 +834,7 @@ static void update_buttons(GtkIMHtmlToolbar *toolbar) { - gboolean bold, italic, underline; + gboolean bold, italic, underline, strike; char *tmp; char *tmp2; GtkLabel *label = g_object_get_data(G_OBJECT(toolbar), "font_label"); @@ -840,6 +843,7 @@ gtk_imhtml_get_current_format(GTK_IMHTML(toolbar->imhtml), &bold, &italic, &underline); + strike = GTK_IMHTML(toolbar->imhtml)->edit.strike; if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->bold)) != bold) toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->bold), bold, @@ -847,10 +851,12 @@ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->italic)) != italic) toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->italic), italic, toolbar); - if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->underline)) != underline) toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->underline), underline, toolbar); + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->strikethrough)) != strike) + toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->strikethrough), + strike, toolbar); /* These buttons aren't ever "active". */ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smaller_size), FALSE); @@ -874,6 +880,12 @@ gtk_label_set_markup_with_mnemonic(label, markup); g_free(markup); } + if (strike) { + gchar *markup = g_strdup_printf("%s", + gtk_label_get_label(label)); + gtk_label_set_markup_with_mnemonic(label, markup); + g_free(markup); + } tmp = gtk_imhtml_get_current_fontface(GTK_IMHTML(toolbar->imhtml)); toggle_button_set_active_block(GTK_TOGGLE_BUTTON(toolbar->font), @@ -1010,9 +1022,41 @@ if (menu) gtk_widget_destroy(menu); + purple_prefs_disconnect_by_handle(object); + G_OBJECT_CLASS(parent_class)->finalize (object); } +static void +switch_toolbar_view(GtkWidget *item, GtkIMHtmlToolbar *toolbar) +{ + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/toolbar/wide", + !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/toolbar/wide")); +} + +static gboolean +gtk_imhtmltoolbar_popup_menu(GtkWidget *widget, GdkEventButton *event, GtkIMHtmlToolbar *toolbar) +{ + GtkWidget *menu; + GtkWidget *item; + gboolean wide; + + if (event->button != 3) + return FALSE; + + wide = GTK_WIDGET_VISIBLE(toolbar->bold); + + menu = gtk_menu_new(); + item = gtk_menu_item_new_with_mnemonic(wide ? _("Group Items") : _("Ungroup Items")); + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(switch_toolbar_view), toolbar); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + gtk_widget_show(item); + + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pidgin_menu_position_func_helper, + widget, event->button, event->time); + return TRUE; +} + /* Boring GTK+ stuff */ static void gtk_imhtmltoolbar_class_init (GtkIMHtmlToolbarClass *class) { @@ -1022,90 +1066,58 @@ gobject_class = (GObjectClass*) class; parent_class = gtk_type_class(GTK_TYPE_HBOX); gobject_class->finalize = gtk_imhtmltoolbar_finalize; + + purple_prefs_add_none(PIDGIN_PREFS_ROOT "/conversations/toolbar"); + purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/conversations/toolbar/wide", FALSE); } static void gtk_imhtmltoolbar_create_old_buttons(GtkIMHtmlToolbar *toolbar) { + GtkWidget *hbox; GtkWidget *button; - /* Bold */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_BOLD); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_bold), toolbar); - toolbar->bold = button; - - - /* Italic */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_ITALIC); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_italic), toolbar); - toolbar->italic = button; - - /* Underline */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_UNDERLINE); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_underline), toolbar); - toolbar->underline = button; - - - /* Strikethrough */ - button = pidgin_pixbuf_toolbar_button_from_stock(GTK_STOCK_STRIKETHROUGH); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_strikethrough), toolbar); - toolbar->strikethrough = button; - - /* Increase font size */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_LARGER); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_big), toolbar); - toolbar->larger_size = button; - - /* Decrease font size */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(do_small), toolbar); - toolbar->smaller_size = button; + struct { + char *stock; + gpointer callback; + GtkWidget **button; + const char *tooltip; + } buttons[] = { + {GTK_STOCK_BOLD, G_CALLBACK(do_bold), &toolbar->bold, _("Bold")}, + {GTK_STOCK_ITALIC, do_italic, &toolbar->italic, _("Italic")}, + {GTK_STOCK_UNDERLINE, do_underline, &toolbar->underline, _("Underline")}, + {GTK_STOCK_STRIKETHROUGH, do_strikethrough, &toolbar->strikethrough, _("Strikethrough")}, + {"", NULL, NULL, NULL}, + {PIDGIN_STOCK_TOOLBAR_TEXT_LARGER, do_big, &toolbar->larger_size, _("Increase Font Size")}, + {PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER, do_small, &toolbar->smaller_size, _("Decrease Font Size")}, + {"", NULL, NULL, NULL}, + {PIDGIN_STOCK_TOOLBAR_FONT_FACE, toggle_font, &toolbar->font, _("Font Face")}, + {PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")}, + {PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")}, + {"", NULL, NULL, NULL}, + {PIDGIN_STOCK_CLEAR, clear_formatting_cb, &toolbar->clear, _("Reset Formatting")}, + {"", NULL, NULL, NULL}, + {PIDGIN_STOCK_TOOLBAR_INSERT_LINK, insert_link_cb, &toolbar->link, _("Insert Link")}, + {PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE, insert_image_cb, &toolbar->image, _("Insert IM Image")}, + {PIDGIN_STOCK_TOOLBAR_SMILEY, insert_smiley_cb, &toolbar->smiley, _("Insert Smiley")}, + {NULL, NULL, NULL, NULL} + }; + int iter; - /* Font Face */ - - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FONT_FACE); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_font), toolbar); - toolbar->font = button; - - /* Foreground Color */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_FGCOLOR); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_fg_color), toolbar); - toolbar->fgcolor = button; - - /* Background Color */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_BGCOLOR); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(toggle_bg_color), toolbar); - toolbar->bgcolor = button; + hbox = gtk_hbox_new(FALSE, 0); - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_LINK); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_link_cb), toolbar); - toolbar->link = button; - - /* Insert IM Image */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_image_cb), toolbar); - toolbar->image = button; + for (iter = 0; buttons[iter].stock; iter++) { + if (buttons[iter].stock[0]) { + button = pidgin_pixbuf_toolbar_button_from_stock(buttons[iter].stock); + g_signal_connect(G_OBJECT(button), "clicked", + G_CALLBACK(buttons[iter].callback), toolbar); + *(buttons[iter].button) = button; + gtk_tooltips_set_tip(toolbar->tooltips, button, buttons[iter].tooltip, NULL); + } else + button = gtk_vseparator_new(); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); + } - /* Insert Smiley */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(insert_smiley_cb), toolbar); - toolbar->smiley = button; - - /* Reset formatting */ - button = pidgin_pixbuf_toolbar_button_from_stock(PIDGIN_STOCK_TOOLBAR_SMILEY); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(clear_formatting_cb), toolbar); - toolbar->clear = button; + gtk_box_pack_start(GTK_BOX(toolbar), hbox, FALSE, FALSE, 0); + g_object_set_data(G_OBJECT(toolbar), "wide-view", hbox); } static void @@ -1129,10 +1141,23 @@ g_object_set(G_OBJECT(widget), "use-markup", TRUE, NULL); } +static void +imhtmltoolbar_view_pref_changed(const char *name, PurplePrefType type, + gconstpointer value, gpointer toolbar) +{ + if (value) { + gtk_widget_hide_all(g_object_get_data(G_OBJECT(toolbar), "lean-view")); + gtk_widget_show_all(g_object_get_data(G_OBJECT(toolbar), "wide-view")); + } else { + gtk_widget_hide_all(g_object_get_data(G_OBJECT(toolbar), "wide-view")); + gtk_widget_show_all(g_object_get_data(G_OBJECT(toolbar), "lean-view")); + } +} + static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar) { - GtkWidget *hbox = GTK_WIDGET(toolbar); - GtkWidget *bbox; + GtkWidget *hbox = GTK_WIDGET(toolbar), *event = gtk_event_box_new(); + GtkWidget *bbox, *box = gtk_hbox_new(FALSE, 0); GtkWidget *image; GtkWidget *label; GtkWidget *insert_button; @@ -1166,7 +1191,6 @@ {NULL, NULL, FALSE} }; - toolbar->imhtml = NULL; toolbar->font_dialog = NULL; toolbar->fgcolor_dialog = NULL; @@ -1192,7 +1216,7 @@ gtk_label_set_use_markup(GTK_LABEL(label), TRUE); g_object_set_data(G_OBJECT(hbox), "font_label", label); gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), font_button, FALSE, FALSE, 0); gtk_widget_show_all(font_button); font_menu = gtk_menu_new(); @@ -1221,7 +1245,7 @@ /* Sep */ sep = gtk_vseparator_new(); - gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), sep, FALSE, FALSE, 0); gtk_widget_show_all(sep); /* Insert */ @@ -1233,7 +1257,7 @@ gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0); label = gtk_label_new_with_mnemonic(_("_Insert")); gtk_box_pack_start(GTK_BOX(bbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox), insert_button, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(box), insert_button, FALSE, FALSE, 0); gtk_widget_show_all(insert_button); insert_menu = gtk_menu_new(); @@ -1266,6 +1290,18 @@ g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu); g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); toolbar->sml = NULL; + + gtk_box_pack_start(GTK_BOX(hbox), box, FALSE, FALSE, 0); + g_object_set_data(G_OBJECT(hbox), "lean-view", box); + + purple_prefs_connect_callback(toolbar, PIDGIN_PREFS_ROOT "/conversations/toolbar/wide", + imhtmltoolbar_view_pref_changed, toolbar); + purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT "/conversations/toolbar/wide"); + + gtk_widget_add_events(event, GDK_BUTTON_PRESS_MASK); + gtk_box_pack_start(GTK_BOX(hbox), event, TRUE, TRUE, 0); + g_signal_connect(G_OBJECT(event), "button-press-event", G_CALLBACK(gtk_imhtmltoolbar_popup_menu), toolbar); + gtk_widget_show(event); } GtkWidget *gtk_imhtmltoolbar_new() diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkmain.c --- a/pidgin/gtkmain.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkmain.c Wed Sep 05 22:32:14 2007 +0000 @@ -768,37 +768,12 @@ purple_set_blist(purple_blist_new()); purple_blist_load(); - /* TODO: Move prefs loading into purple_prefs_init() */ - purple_prefs_load(); - purple_prefs_update_old(); - pidgin_prefs_update_old(); - /* load plugins we had when we quit */ purple_plugins_load_saved(PIDGIN_PREFS_ROOT "/plugins/loaded"); /* TODO: Move pounces loading into purple_pounces_init() */ purple_pounces_load(); - /* Call this early on to try to auto-detect our IP address and - * hopefully save some time later. - * TODO: move this (back) into purple_core_init() when purple_prefs_load() is in purple_prefs_init() */ - purple_network_get_my_ip(-1); - - /* HACK BY SEANEGAN: - * We've renamed prpl-oscar to prpl-aim and prpl-icq, accordingly. - * Let's do that change right here... after everything's loaded, but - * before anything has happened - */ - for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) { - PurpleAccount *account = accounts->data; - if (!strcmp(purple_account_get_protocol_id(account), "prpl-oscar")) { - if (isdigit(*purple_account_get_username(account))) - purple_account_set_protocol_id(account, "prpl-icq"); - else - purple_account_set_protocol_id(account, "prpl-aim"); - } - } - ui_main(); #ifdef USE_SM @@ -837,10 +812,7 @@ g_free(opt_login_arg); opt_login_arg = NULL; } - } - - if (opt_nologin && !opt_login) - { + } else if (opt_nologin) { /* Set all accounts to "offline" */ PurpleSavedStatus *saved_status; @@ -854,9 +826,7 @@ /* Set the status for each account */ purple_savedstatus_activate(saved_status); - } - else if (!opt_login) - { + } else { /* Everything is good to go--sign on already */ if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) purple_savedstatus_activate(purple_savedstatus_get_startup()); diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkpounce.c --- a/pidgin/gtkpounce.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkpounce.c Wed Sep 05 22:32:14 2007 +0000 @@ -975,7 +975,7 @@ purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/pounces/default_actions/play-sound")); } - gtk_widget_show_all(vbox2); + gtk_widget_show(vbox2); gtk_widget_show(window); } diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkprefs.c --- a/pidgin/gtkprefs.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkprefs.c Wed Sep 05 22:32:14 2007 +0000 @@ -887,7 +887,7 @@ ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE); gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER); - + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); vbox = pidgin_make_frame(ret, _("System Tray Icon")); @@ -899,7 +899,7 @@ NULL); gtk_size_group_add_widget(sg, label); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); - + vbox = pidgin_make_frame(ret, _("Conversation Window Hiding")); label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"), PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/im/hide_new", @@ -909,11 +909,11 @@ NULL); gtk_size_group_add_widget(sg, label); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); - + /* All the tab options! */ vbox = pidgin_make_frame(ret, _("Tabs")); - + pidgin_prefs_checkbox(_("Show IMs and chats in _tabbed windows"), PIDGIN_PREFS_ROOT "/conversations/tabs", vbox); @@ -944,12 +944,12 @@ NULL); gtk_size_group_add_widget(sg, label); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); - + names = pidgin_conv_placement_get_options(); label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"), PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/placement", names); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); - + gtk_size_group_add_widget(sg, label); g_list_free(names); @@ -1036,12 +1036,14 @@ gtk_widget_set_sensitive(hbox, FALSE); g_signal_connect(G_OBJECT(fontpref), "clicked", G_CALLBACK(pidgin_toggle_sensitive), hbox); g_signal_connect(G_OBJECT(font_button), "font-set", G_CALLBACK(pidgin_custom_font_set), NULL); + gtk_widget_show_all(hbox); #endif vbox = pidgin_make_frame(ret, _("Default Formatting")); gtk_box_set_child_packing(GTK_BOX(vbox->parent), vbox, TRUE, TRUE, 0, GTK_PACK_START); frame = pidgin_create_imhtml(TRUE, &imhtml, &toolbar, NULL); + gtk_widget_show(frame); gtk_widget_set_name(imhtml, "pidgin_prefs_font_imhtml"); gtk_widget_set_size_request(frame, 300, -1); gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(imhtml), TRUE); @@ -1068,7 +1070,7 @@ G_CALLBACK(formatting_clear_cb), NULL); - gtk_widget_show_all(ret); + gtk_widget_show(ret); return ret; } @@ -1620,7 +1622,7 @@ filename = NULL; purple_request_file(prefs, _("Sound Selection"), filename, FALSE, - G_CALLBACK(sound_chosen_cb), NULL, + G_CALLBACK(sound_chosen_cb), NULL, NULL, NULL, NULL, GINT_TO_POINTER(sound_row_sel)); } @@ -2010,7 +2012,7 @@ return ret; } -static int +static int prefs_notebook_add_page(const char *text, GtkWidget *page, int ind) { @@ -2164,6 +2166,8 @@ /* Smiley Callbacks */ purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme", smiley_theme_pref_cb, NULL); + + pidgin_prefs_update_old(); } void pidgin_prefs_update_old() diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkrequest.c --- a/pidgin/gtkrequest.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkrequest.c Wed Sep 05 22:32:14 2007 +0000 @@ -368,6 +368,8 @@ data->u.input.multiline = multiline; data->u.input.hint = g_strdup(hint); + gtk_widget_show_all(hbox); + if ((data->u.input.hint != NULL) && (!strcmp(data->u.input.hint, "html"))) { GtkWidget *frame; @@ -429,13 +431,14 @@ gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR); } } + gtk_widget_show_all(vbox); } pidgin_set_accessible_label (entry, label); data->u.input.entry = entry; /* Show everything. */ - gtk_widget_show_all(dialog); + gtk_widget_show(dialog); return data; } diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtksound.c --- a/pidgin/gtksound.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtksound.c Wed Sep 05 22:32:14 2007 +0000 @@ -114,7 +114,7 @@ play_conv_event(PurpleConversation *conv, PurpleSoundEventID event) { /* If we should not play the sound for some reason, then exit early */ - if (conv != NULL) + if (conv != NULL && PIDGIN_IS_PIDGIN_CONVERSATION(conv)) { PidginConversation *gtkconv; PidginWindow *win; diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtksourceundomanager.c --- a/pidgin/gtksourceundomanager.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtksourceundomanager.c Wed Sep 05 22:32:14 2007 +0000 @@ -677,8 +677,6 @@ if (um->priv->running_not_undoable_actions > 0) return; - g_return_if_fail (strlen (text) >= (guint)length); - undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT; undo_action.action.insert.pos = gtk_text_iter_get_offset (pos); @@ -774,7 +772,7 @@ *action = *undo_action; if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT) - action->action.insert.text = g_strdup (undo_action->action.insert.text); + action->action.insert.text = g_strndup (undo_action->action.insert.text, undo_action->action.insert.length); else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE) action->action.delete.text = g_strdup (undo_action->action.delete.text); else diff -r b0733d5d7621 -r 1d2002a5735e pidgin/gtkstatusbox.c --- a/pidgin/gtkstatusbox.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/gtkstatusbox.c Wed Sep 05 22:32:14 2007 +0000 @@ -1394,7 +1394,7 @@ return; } gtk_grab_add (box->popup_window); - box->popup_in_progress = TRUE; +// box->popup_in_progress = TRUE; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (box->toggle_button), TRUE); @@ -1590,14 +1590,15 @@ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (status_box->toggle_button))) { pidgin_status_box_popdown (status_box); return TRUE; + } else if (ewidget == status_box->toggle_button) { + status_box->popup_in_progress = TRUE; } /* released outside treeview */ - if (ewidget != status_box->toggle_button) - { + if (ewidget != status_box->toggle_button) { pidgin_status_box_popdown (status_box); return TRUE; - } + } return FALSE; } diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pidgin.h --- a/pidgin/pidgin.h Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/pidgin.h Wed Sep 05 22:32:14 2007 +0000 @@ -26,7 +26,7 @@ #ifndef _PIDGIN_H_ #define _PIDGIN_H_ -#ifndef _WIN32 +#ifdef GDK_WINDOWING_X11 # include #endif diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pidginstock.c --- a/pidgin/pidginstock.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/pidginstock.c Wed Sep 05 22:32:14 2007 +0000 @@ -74,6 +74,11 @@ { PIDGIN_STOCK_SIGN_OFF, NULL, GTK_STOCK_CLOSE }, { PIDGIN_STOCK_TYPED, "pidgin", "typed.png" }, { PIDGIN_STOCK_UPLOAD, NULL, GTK_STOCK_GO_UP }, +#if GTK_CHECK_VERSION(2,8,0) + { PIDGIN_STOCK_INFO, NULL, GTK_STOCK_INFO }, +#else + { PIDGIN_STOCK_INFO, "buttons", "info.png" }, +#endif }; static const GtkStockItem stock_items[] = diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pidginstock.h --- a/pidgin/pidginstock.h Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/pidginstock.h Wed Sep 05 22:32:14 2007 +0000 @@ -45,6 +45,7 @@ #define PIDGIN_STOCK_FILE_CANCELED "pidgin-file-canceled" #define PIDGIN_STOCK_FILE_DONE "pidgin-file-done" #define PIDGIN_STOCK_IGNORE "pidgin-ignore" +#define PIDGIN_STOCK_INFO "pidgin-info" #define PIDGIN_STOCK_INVITE "pidgin-invite" #define PIDGIN_STOCK_MODIFY "pidgin-modify" #define PIDGIN_STOCK_OPEN_MAIL "pidgin-stock-open-mail" diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pixmaps/Makefile.am --- a/pidgin/pixmaps/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/pixmaps/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -2,6 +2,7 @@ EXTRA_DIST = \ edit.png \ + info.png \ logo.png \ pause.png \ arrow-down.xpm \ @@ -12,7 +13,7 @@ pidgin.ico pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons -pidginbuttonpix_DATA = edit.png pause.png +pidginbuttonpix_DATA = edit.png pause.png info.png pidgindistpixdir = $(datadir)/pixmaps/pidgin pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pixmaps/info.png Binary file pidgin/pixmaps/info.png has changed diff -r b0733d5d7621 -r 1d2002a5735e pidgin/pixmaps/status/11/Makefile.mingw --- a/pidgin/pixmaps/status/11/Makefile.mingw Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/pixmaps/status/11/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -18,3 +18,5 @@ cp $(pidginstatuspix_DATA) $(pidginstatuspixdir); \ fi; + $(MAKE) -C rtl -f Makefile.mingw install || exit 1; \ + diff -r b0733d5d7621 -r 1d2002a5735e pidgin/plugins/Makefile.am --- a/pidgin/plugins/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/plugins/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -16,9 +16,13 @@ PERL_DIR = perl endif +if ENABLE_GESTURES +GESTURE_DIR = gestures +endif + SUBDIRS = \ $(CAP_DIR) \ - gestures \ + $(GESTURE_DIR) \ $(GEVOLUTION_DIR) \ $(MUSICMESSAGING_DIR) \ $(PERL_DIR) \ diff -r b0733d5d7621 -r 1d2002a5735e pidgin/plugins/notify.c --- a/pidgin/plugins/notify.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/plugins/notify.c Wed Sep 05 22:32:14 2007 +0000 @@ -167,7 +167,7 @@ gboolean has_focus; PidginWindow *purplewin = NULL; - if (conv == NULL) + if (conv == NULL || PIDGIN_CONVERSATION(conv) == NULL) return 0; /* We want to remove the notifications, but not reset the counter */ @@ -224,6 +224,8 @@ PidginWindow *purplewin = NULL; g_return_if_fail(conv != NULL); + if (PIDGIN_CONVERSATION(conv) == NULL) + return; purplewin = PIDGIN_CONVERSATION(conv)->win; active_conv = pidgin_conv_window_get_active_conversation(purplewin); @@ -417,10 +419,14 @@ deleting_conv(PurpleConversation *conv) { PidginWindow *purplewin = NULL; + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + + if (gtkconv == NULL) + return; detach_signals(conv); - purplewin = PIDGIN_CONVERSATION(conv)->win; + purplewin = gtkconv->win; handle_urgent(purplewin, FALSE); purple_conversation_set_data(conv, "notify-message-count", GINT_TO_POINTER(0)); diff -r b0733d5d7621 -r 1d2002a5735e pidgin/plugins/win32/transparency/win2ktrans.c --- a/pidgin/plugins/win32/transparency/win2ktrans.c Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/plugins/win32/transparency/win2ktrans.c Wed Sep 05 22:32:14 2007 +0000 @@ -400,7 +400,7 @@ g_object_get(G_OBJECT(window), "has-toplevel-focus", &has_focus, NULL); - if (!has_focus) + if (!has_focus || !purple_prefs_get_bool(OPT_WINTRANS_IM_ONFOCUS)) set_conv_window_trans(NULL, win); if (g_signal_handler_find(G_OBJECT(window), G_SIGNAL_MATCH_FUNC, diff -r b0733d5d7621 -r 1d2002a5735e pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Tue Aug 28 19:03:07 2007 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Wed Sep 05 22:32:14 2007 +0000 @@ -503,8 +503,8 @@ ${If} ${IsNT} ${AndIf} ${IsWinNT4} Delete "$INSTDIR\plugins\libsilc.dll" - Delete "$INSTDIR\silcclient.dll" - Delete "$INSTDIR\silc.dll" + Delete "$INSTDIR\libsilcclient-1-1-2.dll" + Delete "$INSTDIR\libsilc-1-1-2.dll" ${EndIf} SetOutPath "$INSTDIR" @@ -692,6 +692,11 @@ DeleteRegValue HKLM "${STARTUP_RUN_KEY}" "Pidgin" ; Remove Language preference info (TODO: check if NSIS removes this) + Delete "$INSTDIR\ca-certs\Equifax_Secure_CA.pem" + Delete "$INSTDIR\ca-certs\GTE_CyberTrust_Global_Root.pem" + Delete "$INSTDIR\ca-certs\Verisign_Class3_Primary_CA.pem" + Delete "$INSTDIR\ca-certs\Verisign_RSA_Secure_Server_CA.pem" + RMDir "$INSTDIR\ca-certs" RMDir /r "$INSTDIR\locale" RMDir /r "$INSTDIR\pixmaps" RMDir /r "$INSTDIR\perlmod" @@ -761,8 +766,9 @@ Delete "$INSTDIR\pidgin.dll" Delete "$INSTDIR\plc4.dll" Delete "$INSTDIR\plds4.dll" - Delete "$INSTDIR\silc.dll" - Delete "$INSTDIR\silcclient.dll" + Delete "$INSTDIR\libsilc-1-1-2.dll" + Delete "$INSTDIR\libsilcclient-1-1-2.dll" + Delete "$INSTDIR\smime3.dll" Delete "$INSTDIR\softokn3.dll" Delete "$INSTDIR\ssl3.dll" Delete "$INSTDIR\${PIDGIN_UNINST_EXE}" diff -r b0733d5d7621 -r 1d2002a5735e share/Makefile.am --- a/share/Makefile.am Tue Aug 28 19:03:07 2007 +0000 +++ b/share/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -1,4 +1,4 @@ -SUBDIRS = sounds +SUBDIRS = sounds ca-certs EXTRA_DIST = Makefile.mingw diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/Equifax_Secure_CA.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Equifax_Secure_CA.pem Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQG +EwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1 +cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4 +MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgx +LTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0 +eTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2R +FGiYCh7+2gRvE4RiIcPRfM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO +/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuv +K9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAGA1UdHwRp +MGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEt +MCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjAL +BgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gjIBBPM5iQn9Qw +HQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMBAf8w +GgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GB +AFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2u +FHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE----- diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/GTE_CyberTrust_Global_Root.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/GTE_CyberTrust_Global_Root.pem Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgw +FgYDVQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRy +dXN0IFNvbHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3Qg +R2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1 +MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYD +VQQLEx5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMT +GkdURSBDeWJlclRydXN0IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4usJTQGz0O9pTAipTHBsiQl8i4 +ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcqlHHK6XALn +ZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8F +LztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh3 +46B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq +81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0PlZPvy5TYnh+d +XIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Makefile.am Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,11 @@ +cacertsdir = $(datadir)/purple/ca-certs +cacerts_DATA = \ + Equifax_Secure_CA.pem \ + GTE_CyberTrust_Global_Root.pem \ + Verisign_RSA_Secure_Server_CA.pem \ + Verisign_Class3_Primary_CA.pem + +EXTRA_DIST = \ + Makefile.mingw \ + $(cacerts_DATA) + diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/Makefile.mingw --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Makefile.mingw Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,21 @@ +# +# Makefile.mingw +# +# Description: Makefile for win32 (mingw) version of Pidgin ca-certs +# + +PIDGIN_TREE_TOP := ../.. +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak + +datadir := $(PIDGIN_INSTALL_DIR) +include ./Makefile.am +cacertsdir := $(PIDGIN_INSTALL_DIR)/ca-certs + +.PHONY: install + +install: + if test '$(cacerts_DATA)'; then \ + mkdir -p $(cacertsdir); \ + cp $(cacerts_DATA) $(cacertsdir); \ + fi; + diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/Verisign_Class3_Primary_CA.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Verisign_Class3_Primary_CA.pem Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- diff -r b0733d5d7621 -r 1d2002a5735e share/ca-certs/Verisign_RSA_Secure_Server_CA.pem --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/share/ca-certs/Verisign_RSA_Secure_Server_CA.pem Wed Sep 05 22:32:14 2007 +0000 @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzEL +MAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMu +MS4wLAYDVQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVowXzELMAkGA1UE +BhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYD +VQQLEyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGb +MA0GCSqGSIb3DQEBAQUAA4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6O +LDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1XpsSECrXZogZoFokvJSyVmIlZs +iAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJVCxzOmmC +sZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw +4jrg7HFGmhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxr +l0nHPjXcbLm7qt9cuzovk2C2qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1 +g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== +-----END CERTIFICATE-----