# HG changeset patch # User Yoshiki Yazawa # Date 1261036212 -32400 # Node ID 4491a662d5273595c91be498ab72e7893a63de1c # Parent 5c77b620375cda3073faec2b62afea3bf1ecefbf# Parent afebd4e2fc1a0a23cf7e4a728417291baa6cbed7 merged with im.pidgin.pidgin diff -r 5c77b620375c -r 4491a662d527 ChangeLog --- a/ChangeLog Mon Nov 30 16:07:54 2009 +0900 +++ b/ChangeLog Thu Dec 17 16:50:12 2009 +0900 @@ -1,7 +1,34 @@ - Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul version 2.6.5 (??/??/20??): + libpurple: + * TLS certificates are actually stored to the local cache once again + (accepting a name mismatch on a certificate should now be remembered) + + General: + * Build-time fixes for Solaris. (Paul Townsend) + + AIM and ICQ: + * Messages from some mobile clients are no longer displayed as + Chinese characters (broken in 2.6.4) + + MSN: + * File transfer requests will no longer cause a crash if you delete the + file before the other side accepts. + * Recieved files will no longer hold an extra lock after completion, + meaning they can be moved or deleted without complaints from your OS. + * Buddies who sign in from a second location will no longer cause an + unnecessary chat window to open. + + XMPP: + * Added support for the SCRAM-SHA-1 SASL mechanism. This is only + available when built without Cyrus SASL support. + * When getting info on a domain-only (server) JID, show uptime + (when given by the result of the "last query") and don't show status as + offline. + * Do not crash when attempting to register for a new account on Windows. + * Fix file transfer with clients that do not support Entity Capabilities + (e.g. Spark) version 2.6.4 (11/29/2009): libpurple: diff -r 5c77b620375c -r 4491a662d527 ChangeLog.win32 --- a/ChangeLog.win32 Mon Nov 30 16:07:54 2009 +0900 +++ b/ChangeLog.win32 Thu Dec 17 16:50:12 2009 +0900 @@ -1,4 +1,5 @@ version 2.6.5 (??/??/20??): + * Installer translations for: Norwegian nynorsk version 2.6.4 (11/29/2009): * Register URL handlers for everything that Windows knows about. Still diff -r 5c77b620375c -r 4491a662d527 configure.ac --- a/configure.ac Mon Nov 30 16:07:54 2009 +0900 +++ b/configure.ac Thu Dec 17 16:50:12 2009 +0900 @@ -1470,7 +1470,7 @@ AC_CHECK_LIB(pthread, pthread_create, ) AC_CHECK_LIB(util, openpty, ) AC_CHECK_LIB(db, dbopen, ) - PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config" + PY_LIBS="-L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config -lpython$PY_VERSION" PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION" AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.]) AC_MSG_RESULT(ok) @@ -2267,11 +2267,15 @@ AC_ARG_ENABLE(cyrus-sasl, AC_HELP_STRING([--enable-cyrus-sasl], [enable Cyrus SASL support for jabberd]), enable_cyrus_sasl=$enableval, enable_cyrus_sasl=no) if test "x$enable_cyrus_sasl" = "xyes" ; then AC_CHECK_LIB(sasl2, sasl_client_init, [ + AM_CONDITIONAL(USE_CYRUS_SASL, true) AC_DEFINE(HAVE_CYRUS_SASL, [1], [Define to 1 if Cyrus SASL is present]) SASL_LIBS=-"lsasl2" ], [ + AM_CONDITIONAL(USE_CYRUS_SASL, false) AC_ERROR(Cyrus SASL library not found) ]) +else + AM_CONDITIONAL(USE_CYRUS_SASL, false) fi dnl ####################################################################### diff -r 5c77b620375c -r 4491a662d527 finch/finch.c --- a/finch/finch.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/finch.c Thu Dec 17 16:50:12 2009 +0900 @@ -19,8 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "account.h" #include "conversation.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntaccount.c --- a/finch/gntaccount.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntaccount.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -36,7 +38,6 @@ #include #include "finch.h" -#include #include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntblist.c --- a/finch/gntblist.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntblist.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntcertmgr.c --- a/finch/gntcertmgr.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntcertmgr.c Thu Dec 17 16:50:12 2009 +0900 @@ -25,8 +25,8 @@ * */ +#include #include "finch.h" -#include #include "certificate.h" #include "debug.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntconn.c --- a/finch/gntconn.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntconn.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "account.h" #include "core.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntconv.c --- a/finch/gntconv.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntconv.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include +#include #include "finch.h" -#include #include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntdebug.c --- a/finch/gntdebug.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntdebug.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,6 +23,9 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include + #include #include #include @@ -35,7 +38,6 @@ #include "gntdebug.h" #include "finch.h" -#include #include "notify.h" #include "util.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntft.c --- a/finch/gntft.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntft.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntlog.c --- a/finch/gntlog.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntlog.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,8 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntmedia.c --- a/finch/gntmedia.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntmedia.c Thu Dec 17 16:50:12 2009 +0900 @@ -24,8 +24,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include #include "finch.h" -#include #include "gntconv.h" #include "gntmedia.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntnotify.c --- a/finch/gntnotify.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntnotify.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -32,7 +34,6 @@ #include #include "finch.h" -#include #include diff -r 5c77b620375c -r 4491a662d527 finch/gntplugin.c --- a/finch/gntplugin.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntplugin.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,6 +23,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include + #include #include #include @@ -32,7 +34,6 @@ #include #include "finch.h" -#include #include "debug.h" #include "notify.h" diff -r 5c77b620375c -r 4491a662d527 finch/gntpounce.c --- a/finch/gntpounce.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/gntpounce.c Thu Dec 17 16:50:12 2009 +0900 @@ -24,6 +24,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ +#include + #include #include #include @@ -36,7 +38,6 @@ #include #include "finch.h" -#include #include "account.h" #include "conversation.h" diff -r 5c77b620375c -r 4491a662d527 finch/libgnt/gntkeys.h --- a/finch/libgnt/gntkeys.h Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/libgnt/gntkeys.h Thu Dec 17 16:50:12 2009 +0900 @@ -65,7 +65,7 @@ #define GNT_KEY_BACKSPACE SAFE(key_backspace) #define GNT_KEY_DEL SAFE(key_dc) #define GNT_KEY_INS SAFE(key_ic) -#define GNT_KEY_BACK_TAB SAFE(back_tab) +#define GNT_KEY_BACK_TAB (back_tab ? back_tab : SAFE(key_btab)) #define GNT_KEY_CTRL_A "\001" #define GNT_KEY_CTRL_B "\002" diff -r 5c77b620375c -r 4491a662d527 finch/libgnt/gntutils.c --- a/finch/libgnt/gntutils.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/libgnt/gntutils.c Thu Dec 17 16:50:12 2009 +0900 @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "config.h" + #include "gntinternal.h" #undef GNT_LOG_DOMAIN #define GNT_LOG_DOMAIN "Utils" @@ -35,8 +37,6 @@ #include "gntutils.h" #include "gntwindow.h" -#include "config.h" - #include #include #include @@ -46,8 +46,6 @@ #include #endif -#include "config.h" - void gnt_util_get_text_bound(const char *text, int *width, int *height) { const char *s = text, *last; diff -r 5c77b620375c -r 4491a662d527 finch/libgnt/wms/s.c --- a/finch/libgnt/wms/s.c Mon Nov 30 16:07:54 2009 +0900 +++ b/finch/libgnt/wms/s.c Thu Dec 17 16:50:12 2009 +0900 @@ -1,8 +1,8 @@ +#include "internal.h" + #include #include -#include "internal.h" - #include "gnt.h" #include "gntbox.h" #include "gntmenu.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/certificate.c --- a/libpurple/certificate.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/certificate.c Thu Dec 17 16:50:12 2009 +0900 @@ -1431,9 +1431,8 @@ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, "tls_peers"); if (tls_peers) { - if (!purple_certificate_pool_contains(tls_peers, vrq->subject_name) && - !purple_certificate_pool_store(tls_peers,vrq->subject_name, - peer_crt)) { + 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"); } diff -r 5c77b620375c -r 4491a662d527 libpurple/conversation.h --- a/libpurple/conversation.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/conversation.h Thu Dec 17 16:50:12 2009 +0900 @@ -1025,7 +1025,8 @@ GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users); /** - * Returns a list of users in the chat room. + * Returns a list of users in the chat room. The members of the list + * are PurpleConvChatBuddy objects. * * @param chat The chat. * diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/common.c --- a/libpurple/protocols/gg/lib/common.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/common.c Thu Dec 17 16:50:12 2009 +0900 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #ifndef _WIN32 #include #include @@ -41,8 +43,6 @@ #include #include -#include "libgadu.h" - FILE *gg_debug_file = NULL; #ifndef GG_DEBUG_DISABLE diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/dcc.c --- a/libpurple/protocols/gg/lib/dcc.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/dcc.c Thu Dec 17 16:50:12 2009 +0900 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #include #include #ifndef _WIN32 @@ -41,8 +43,6 @@ #include #include "compat.h" -#include "libgadu.h" - #ifndef GG_DEBUG_DISABLE /* * gg_dcc_debug_data() // funkcja wewntrzna diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/events.c --- a/libpurple/protocols/gg/lib/events.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/events.c Thu Dec 17 16:50:12 2009 +0900 @@ -20,6 +20,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -46,7 +48,6 @@ #endif #include "compat.h" -#include "libgadu.h" /* * gg_event_free() diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/http.c --- a/libpurple/protocols/gg/lib/http.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/http.c Thu Dec 17 16:50:12 2009 +0900 @@ -18,6 +18,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -43,8 +45,6 @@ #include #include "compat.h" -#include "libgadu.h" -#include /* * gg_http_connect() // funkcja pomocnicza diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/libgadu.c --- a/libpurple/protocols/gg/lib/libgadu.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/libgadu.c Thu Dec 17 16:50:12 2009 +0900 @@ -21,6 +21,8 @@ * USA. */ +#include "libgadu.h" + #include #ifndef _WIN32 #include @@ -57,7 +59,6 @@ #endif #include "compat.h" -#include "libgadu.h" int gg_debug_level = 0; void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/pubdir.c --- a/libpurple/protocols/gg/lib/pubdir.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/pubdir.c Thu Dec 17 16:50:12 2009 +0900 @@ -19,6 +19,8 @@ * USA. */ +#include "libgadu.h" + #include #include #include @@ -27,8 +29,6 @@ #include #include -#include "libgadu.h" - /* * gg_register3() * diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/gg/lib/pubdir50.c --- a/libpurple/protocols/gg/lib/pubdir50.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/gg/lib/pubdir50.c Thu Dec 17 16:50:12 2009 +0900 @@ -18,14 +18,14 @@ * USA. */ +#include "libgadu.h" + #include #include #include #include #include -#include "libgadu.h" - /* * gg_pubdir50_new() * diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/Makefile.am --- a/libpurple/protocols/jabber/Makefile.am Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/Makefile.am Thu Dec 17 16:50:12 2009 +0900 @@ -10,6 +10,10 @@ adhoccommands.h \ auth.c \ auth.h \ + auth_digest_md5.c \ + auth_plain.c \ + auth_scram.c \ + auth_scram.h \ buddy.c \ buddy.h \ bosh.c \ @@ -78,6 +82,10 @@ libxmpp_la_LDFLAGS = -module -avoid-version +if USE_CYRUS_SASL +JABBERSOURCES += auth_cyrus.c +endif + if STATIC_JABBER st = -DPURPLE_STATIC_PRPL diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/Makefile.mingw --- a/libpurple/protocols/jabber/Makefile.mingw Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/Makefile.mingw Thu Dec 17 16:50:12 2009 +0900 @@ -45,6 +45,10 @@ C_SRC = \ adhoccommands.c \ auth.c \ + auth_cyrus.c \ + auth_digest_md5.c \ + auth_plain.c \ + auth_scram.c \ buddy.c \ bosh.c \ caps.c \ diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/auth.c Thu Dec 17 16:50:12 2009 +0900 @@ -39,6 +39,8 @@ #include "iq.h" #include "notify.h" +static GSList *auth_mechs = NULL; + static void auth_old_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer data); @@ -46,8 +48,11 @@ gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet) { + PurpleAccount *account; xmlnode *starttls; + account = purple_connection_get_account(js->gc); + if((starttls = xmlnode_get_child(packet, "starttls"))) { if(purple_ssl_is_supported()) { jabber_send_raw(js, @@ -58,7 +63,7 @@ PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; - } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + } else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); @@ -71,418 +76,96 @@ static void finish_plaintext_authentication(JabberStream *js) { - if(js->auth_type == JABBER_AUTH_PLAIN) { - xmlnode *auth; - GString *response; - gchar *enc_out; - - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - - xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); - xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); - - response = g_string_new(""); - response = g_string_append_len(response, "\0", 1); - response = g_string_append(response, js->user->node); - response = g_string_append_len(response, "\0", 1); - response = g_string_append(response, - purple_connection_get_password(js->gc)); - - enc_out = purple_base64_encode((guchar *)response->str, response->len); + JabberIq *iq; + xmlnode *query, *x; - xmlnode_set_attrib(auth, "mechanism", "PLAIN"); - xmlnode_insert_data(auth, enc_out, -1); - g_free(enc_out); - g_string_free(response, TRUE); - - jabber_send(js, auth); - xmlnode_free(auth); - } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { - JabberIq *iq; - xmlnode *query, *x; - - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); - query = xmlnode_get_child(iq->node, "query"); - x = xmlnode_new_child(query, "username"); - xmlnode_insert_data(x, js->user->node, -1); - x = xmlnode_new_child(query, "resource"); - xmlnode_insert_data(x, js->user->resource, -1); - x = xmlnode_new_child(query, "password"); - xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1); - jabber_iq_set_callback(iq, auth_old_result_cb, NULL); - jabber_iq_send(iq); - } + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + x = xmlnode_new_child(query, "password"); + xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1); + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); + jabber_iq_send(iq); } static void allow_plaintext_auth(PurpleAccount *account) { + PurpleConnection *gc; + JabberStream *js; + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); - finish_plaintext_authentication(account->gc->proto_data); + gc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(gc); + + finish_plaintext_authentication(js); } static void disallow_plaintext_auth(PurpleAccount *account) { - purple_connection_error_reason(account->gc, + purple_connection_error_reason(purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("Server requires plaintext authentication over an unencrypted stream")); } #ifdef HAVE_CYRUS_SASL - -static void jabber_auth_start_cyrus(JabberStream *); -static void jabber_sasl_build_callbacks(JabberStream *); - -/* Callbacks for Cyrus SASL */ - -static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result) -{ - JabberStream *js = (JabberStream *)ctx; - - if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM; - - *result = js->user->domain; - - return SASL_OK; -} - -static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len) +static void +auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) { - JabberStream *js = (JabberStream *)ctx; - - switch(id) { - case SASL_CB_AUTHNAME: - *res = js->user->node; - break; - case SASL_CB_USER: - *res = ""; - break; - default: - return SASL_BADPARAM; - } - if (len) *len = strlen((char *)*res); - return SASL_OK; -} - -static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret) -{ - JabberStream *js = (JabberStream *)ctx; - const char *pw = purple_account_get_password(js->gc->account); - size_t len; - static sasl_secret_t *x = NULL; - - if (!conn || !secret || id != SASL_CB_PASS) - return SASL_BADPARAM; - - len = strlen(pw); - x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); - - if (!x) - return SASL_NOMEM; - - x->len = len; - strcpy((char*)x->data, pw); - - *secret = x; - return SASL_OK; -} - -static void allow_cyrus_plaintext_auth(PurpleAccount *account) -{ - purple_account_set_bool(account, "auth_plain_in_clear", TRUE); - - jabber_auth_start_cyrus(account->gc->proto_data); -} - -static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields) -{ + PurpleAccount *account; + JabberStream *js; const char *entry; gboolean remember; + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + entry = purple_request_fields_get_string(fields, "password"); remember = purple_request_fields_get_bool(fields, "remember"); if (!entry || !*entry) { - purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL); - return FALSE; + purple_notify_error(account, NULL, _("Password is required to sign on."), NULL); + return; } if (remember) - purple_account_set_remember_password(js->gc->account, TRUE); - - purple_account_set_password(js->gc->account, entry); - - return TRUE; -} - -static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) -{ - JabberStream *js; - - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) - return; - - js = conn->proto_data; - - if (!auth_pass_generic(js, fields)) - return; + purple_account_set_remember_password(account, TRUE); - /* Rebuild our callbacks as we now have a password to offer */ - jabber_sasl_build_callbacks(js); - - /* Restart our connection */ - jabber_auth_start_cyrus(js); -} - -static void -auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) -{ - JabberStream *js; - - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) - return; - - js = conn->proto_data; - - if (!auth_pass_generic(js, fields)) - return; + purple_account_set_password(account, entry); /* Restart our connection */ jabber_auth_start_old(js); } - static void -auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) +auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) { - JabberStream *js; - /* The password prompt dialog doesn't get disposed if the account disconnects */ - if (!PURPLE_CONNECTION_IS_VALID(conn)) + if (!PURPLE_CONNECTION_IS_VALID(gc)) return; - js = conn->proto_data; - /* Disable the account as the user has canceled connecting */ - purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE); + purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE); } - -static void jabber_auth_start_cyrus(JabberStream *js) -{ - const char *clientout = NULL; - char *enc_out; - unsigned coutlen = 0; - xmlnode *auth; - sasl_security_properties_t secprops; - gboolean again; - gboolean plaintext = TRUE; - - /* Set up security properties and options */ - secprops.min_ssf = 0; - secprops.security_flags = SASL_SEC_NOANONYMOUS; - - if (!jabber_stream_is_ssl(js)) { - secprops.max_ssf = -1; - secprops.maxbufsize = 4096; - plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE); - if (!plaintext) - secprops.security_flags |= SASL_SEC_NOPLAINTEXT; - } else { - secprops.max_ssf = 0; - secprops.maxbufsize = 0; - plaintext = TRUE; - } - secprops.property_names = 0; - secprops.property_values = 0; - - do { - again = FALSE; - - js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl); - if (js->sasl_state==SASL_OK) { - sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops); - purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str); - js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech); - } - switch (js->sasl_state) { - /* Success */ - case SASL_OK: - case SASL_CONTINUE: - break; - case SASL_NOMECH: - /* No mechanisms have offered to help */ - - /* Firstly, if we don't have a password try - * to get one - */ - - if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); - return; - - /* If we've got a password, but aren't sending - * it in plaintext, see if we can turn on - * plaintext auth - */ - } else if (!plaintext) { - char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); - purple_request_yes_no(js->gc, _("Plaintext Authentication"), - _("Plaintext Authentication"), - msg, - 1, js->gc->account, NULL, NULL, js->gc->account, - allow_cyrus_plaintext_auth, - disallow_plaintext_auth); - g_free(msg); - return; - - } else { - /* We have no mechs which can work. - * Try falling back on the old jabber:iq:auth method. We get here if the server supports - * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of - * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect - * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often - * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. - * - * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, - * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. - * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers - * which would connect without issue otherwise. -evands - */ - js->auth_type = JABBER_AUTH_IQ_AUTH; - jabber_auth_start_old(js); - return; - } - /* not reached */ - break; - - /* Fatal errors. Give up and go home */ - case SASL_BADPARAM: - case SASL_NOMEM: - break; - - /* For everything else, fail the mechanism and try again */ - default: - purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state); - - /* - * DAA: is this right? - * The manpage says that "mech" will contain the chosen mechanism on success. - * Presumably, if we get here that isn't the case and we shouldn't try again? - * I suspect that this never happens. - */ - /* - * SXW: Yes, this is right. What this handles is the situation where a - * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be - * due to mechanism specific issues, so we want to try one of the other - * supported mechanisms. This code handles that case - */ - if (js->current_mech && *js->current_mech) { - char *pos; - if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { - g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); - } - /* Remove space which separated this mech from the next */ - if ((js->sasl_mechs->str)[0] == ' ') { - g_string_erase(js->sasl_mechs, 0, 1); - } - again = TRUE; - } - - sasl_dispose(&js->sasl); - } - } while (again); - - if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) { - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - xmlnode_set_attrib(auth, "mechanism", js->current_mech); - - xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); - xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); - - if (clientout) { - if (coutlen == 0) { - xmlnode_insert_data(auth, "=", -1); - } else { - enc_out = purple_base64_encode((unsigned char*)clientout, coutlen); - xmlnode_insert_data(auth, enc_out, -1); - g_free(enc_out); - } - } - jabber_send(js, auth); - xmlnode_free(auth); - } else { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, - _("SASL authentication failed")); - } -} - -static int -jabber_sasl_cb_log(void *context, int level, const char *message) -{ - if(level <= SASL_LOG_TRACE) - purple_debug_info("sasl", "%s\n", message); - - return SASL_OK; -} - -void -jabber_sasl_build_callbacks(JabberStream *js) -{ - int id; - - /* Set up our callbacks structure */ - if (js->sasl_cb == NULL) - js->sasl_cb = g_new0(sasl_callback_t,6); - - id = 0; - js->sasl_cb[id].id = SASL_CB_GETREALM; - js->sasl_cb[id].proc = jabber_sasl_cb_realm; - js->sasl_cb[id].context = (void *)js; - id++; - - js->sasl_cb[id].id = SASL_CB_AUTHNAME; - js->sasl_cb[id].proc = jabber_sasl_cb_simple; - js->sasl_cb[id].context = (void *)js; - id++; - - js->sasl_cb[id].id = SASL_CB_USER; - js->sasl_cb[id].proc = jabber_sasl_cb_simple; - js->sasl_cb[id].context = (void *)js; - id++; - - if (purple_account_get_password(js->gc->account) != NULL ) { - js->sasl_cb[id].id = SASL_CB_PASS; - js->sasl_cb[id].proc = jabber_sasl_cb_secret; - js->sasl_cb[id].context = (void *)js; - id++; - } - - js->sasl_cb[id].id = SASL_CB_LOG; - js->sasl_cb[id].proc = jabber_sasl_cb_log; - js->sasl_cb[id].context = (void*)js; - id++; - - js->sasl_cb[id].id = SASL_CB_LIST_END; -} - #endif void jabber_auth_start(JabberStream *js, xmlnode *packet) { -#ifndef HAVE_CYRUS_SASL - gboolean digest_md5 = FALSE, plain=FALSE; -#endif - + GSList *mechanisms = NULL; + GSList *l; + xmlnode *response = NULL; xmlnode *mechs, *mechnode; - + JabberSaslState state; + char *msg = NULL; if(js->registration) { jabber_register_start(js); @@ -490,7 +173,6 @@ } mechs = xmlnode_get_child(packet, "mechanisms"); - if(!mechs) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, @@ -498,76 +180,53 @@ return; } -#ifdef HAVE_CYRUS_SASL - js->sasl_mechs = g_string_new(""); -#endif - for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; mechnode = xmlnode_get_next_twin(mechnode)) { char *mech_name = xmlnode_get_data(mechnode); -#ifdef HAVE_CYRUS_SASL - /* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not - * support it and including it gives a false fall-back to other mechs offerred, - * leading to incorrect error handling. - */ - if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) { - g_free(mech_name); - continue; - } - g_string_append(js->sasl_mechs, mech_name); - g_string_append_c(js->sasl_mechs, ' '); -#else - if (purple_strequal(mech_name, "DIGEST-MD5")) - digest_md5 = TRUE; - else if (purple_strequal(mech_name, "PLAIN")) - plain = TRUE; -#endif - g_free(mech_name); + if (mech_name && *mech_name) + mechanisms = g_slist_prepend(mechanisms, mech_name); + else if (mech_name) + g_free(mech_name); + } -#ifdef HAVE_CYRUS_SASL - js->auth_type = JABBER_AUTH_CYRUS; - - jabber_sasl_build_callbacks(js); - - jabber_auth_start_cyrus(js); -#else + for (l = auth_mechs; l; l = l->next) { + JabberSaslMech *possible = l->data; - if(digest_md5) { - xmlnode *auth; - - js->auth_type = JABBER_AUTH_DIGEST_MD5; - auth = xmlnode_new("auth"); - xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); - xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); + /* Is this the Cyrus SASL mechanism? */ + if (g_str_equal(possible->name, "*")) { + js->auth_mech = possible; + break; + } - jabber_send(js, auth); - xmlnode_free(auth); - } else if(plain) { - js->auth_type = JABBER_AUTH_PLAIN; + /* Can we find this mechanism in the server's list? */ + if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) { + js->auth_mech = possible; + break; + } + } - if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) { - char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); - purple_request_yes_no(js->gc, _("Plaintext Authentication"), - _("Plaintext Authentication"), - msg, - 1, - purple_connection_get_account(js->gc), NULL, NULL, - purple_connection_get_account(js->gc), allow_plaintext_auth, - disallow_plaintext_auth); - g_free(msg); - return; - } - finish_plaintext_authentication(js); - } else { + if (js->auth_mech == NULL) { + /* Found no good mechanisms... */ purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); + return; } -#endif + + state = js->auth_mech->start(js, mechs, &response, &msg); + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Unknown Error")); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } + + g_free(msg); } static void auth_old_result_cb(JabberStream *js, const char *from, @@ -578,19 +237,22 @@ jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH); jabber_disco_items_server(js); } else { + PurpleAccount *account; PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); xmlnode *error; const char *err_code; + account = purple_connection_get_account(js->gc); + /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && g_str_equal(err_code, "401")) { reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ - if (!purple_account_get_remember_password(js->gc->account)) - purple_account_set_password(js->gc->account, NULL); + if (!purple_account_get_remember_password(account)) + purple_account_set_password(account, NULL); } purple_connection_error_reason(js->gc, reason, msg); @@ -663,16 +325,17 @@ jabber_iq_send(iq); } else if(xmlnode_get_child(query, "password")) { - if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, + PurpleAccount *account = purple_connection_get_account(js->gc); + if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) { char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), - js->gc->account->username); + purple_account_get_username(account)); purple_request_yes_no(js->gc, _("Plaintext Authentication"), _("Plaintext Authentication"), msg, 1, - purple_connection_get_account(js->gc), NULL, NULL, - purple_connection_get_account(js->gc), allow_plaintext_auth, + account, NULL, NULL, + account, allow_plaintext_auth, disallow_plaintext_auth); g_free(msg); return; @@ -689,22 +352,30 @@ void jabber_auth_start_old(JabberStream *js) { + PurpleAccount *account; JabberIq *iq; xmlnode *query, *username; + account = purple_connection_get_account(js->gc); + /* * We can end up here without encryption if the server doesn't support * and we're not using old-style SSL. If the user * is requiring SSL/TLS, we need to enforce it. */ if (!jabber_stream_is_ssl(js) && - purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); return; } + if (js->registration) { + jabber_register_start(js); + return; + } + /* * IQ Auth doesn't have support for resource binding, so we need to pick a * default resource so it will work properly. jabberd14 throws an error and @@ -721,8 +392,8 @@ * password prompting here */ - if (!purple_account_get_password(js->gc->account)) { - purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); + if (!purple_account_get_password(account)) { + purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); return; } #endif @@ -737,352 +408,65 @@ jabber_iq_send(iq); } -/* Parts of this algorithm are inspired by stuff in libgsasl */ -static GHashTable* parse_challenge(const char *challenge) -{ - const char *token_start, *val_start, *val_end, *cur; - GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); - - cur = challenge; - while(*cur != '\0') { - /* Find the end of the token */ - gboolean in_quotes = FALSE; - char *name, *value = NULL; - token_start = cur; - while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) { - if (*cur == '"') - in_quotes = !in_quotes; - cur++; - } - - /* Find start of value. */ - val_start = strchr(token_start, '='); - if (val_start == NULL || val_start > cur) - val_start = cur; - - if (token_start != val_start) { - name = g_strndup(token_start, val_start - token_start); - - if (val_start != cur) { - val_start++; - while (val_start != cur && (*val_start == ' ' || *val_start == '\t' - || *val_start == '\r' || *val_start == '\n' - || *val_start == '"')) - val_start++; - - val_end = cur; - while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t' - || *val_end == '\r' || *val_end == '\n' - || *val_end == '"' || *val_end == '\0')) - val_end--; - - if (val_start != val_end) - value = g_strndup(val_start, val_end - val_start + 1); - } - - g_hash_table_replace(ret, name, value); - } - - /* Find the start of the next token, if there is one */ - if (*cur != '\0') { - cur++; - while (*cur == ' ' || *cur == ',' || *cur == '\t' - || *cur == '\r' || *cur == '\n') - cur++; - } - } - - return ret; -} - -static char * -generate_response_value(JabberID *jid, const char *passwd, const char *nonce, - const char *cnonce, const char *a2, const char *realm) -{ - PurpleCipher *cipher; - PurpleCipherContext *context; - guchar result[16]; - size_t a1len; - - gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; - - if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", - NULL, NULL, NULL)) == NULL) { - convnode = g_strdup(jid->node); - } - if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", - "utf-8", NULL, NULL, NULL)) == NULL)) { - convpasswd = g_strdup(passwd); - } - - cipher = purple_ciphers_find_cipher("md5"); - context = purple_cipher_context_new(cipher, NULL); - - x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : ""); - purple_cipher_context_append(context, (const guchar *)x, strlen(x)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); - a1len = strlen(a1); - g_memmove(a1, result, 16); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)a1, a1len); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - ha1 = purple_base16_encode(result, 16); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)a2, strlen(a2)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - - ha2 = purple_base16_encode(result, 16); - - kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); - - purple_cipher_context_reset(context, NULL); - purple_cipher_context_append(context, (const guchar *)kd, strlen(kd)); - purple_cipher_context_digest(context, sizeof(result), result, NULL); - purple_cipher_context_destroy(context); - - z = purple_base16_encode(result, 16); - - g_free(convnode); - g_free(convpasswd); - g_free(x); - g_free(a1); - g_free(ha1); - g_free(ha2); - g_free(kd); - - return z; -} - void jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) { - - if(js->auth_type == JABBER_AUTH_DIGEST_MD5) { - char *enc_in = xmlnode_get_data(packet); - char *dec_in; - char *enc_out; - GHashTable *parts; - - if(!enc_in) { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid response from server")); - return; - } - - dec_in = (char *)purple_base64_decode(enc_in, NULL); - purple_debug_misc("jabber", "decoded challenge (%" - G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); - - parts = parse_challenge(dec_in); - - - if (g_hash_table_lookup(parts, "rspauth")) { - char *rspauth = g_hash_table_lookup(parts, "rspauth"); - - - if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { - jabber_send_raw(js, - "", - -1); - } else { - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid challenge from server")); - } - g_free(js->expected_rspauth); - js->expected_rspauth = NULL; - } else { - /* assemble a response, and send it */ - /* see RFC 2831 */ - char *realm; - char *nonce; - - /* Make sure the auth string contains everything that should be there. - This isn't everything in RFC2831, but it is what we need. */ - - nonce = g_hash_table_lookup(parts, "nonce"); - - /* we're actually supposed to prompt the user for a realm if - * the server doesn't send one, but that really complicates things, - * so i'm not gonna worry about it until is poses a problem to - * someone, or I get really bored */ - realm = g_hash_table_lookup(parts, "realm"); - if(!realm) - realm = js->user->domain; - - if (nonce == NULL || realm == NULL) - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid challenge from server")); - else { - GString *response = g_string_new(""); - char *a2; - char *auth_resp; - char *buf; - char *cnonce; - - cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), - g_random_int()); - - a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); - auth_resp = generate_response_value(js->user, - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); - g_free(a2); - - a2 = g_strdup_printf(":xmpp/%s", realm); - js->expected_rspauth = generate_response_value(js->user, - purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); - g_free(a2); - - g_string_append_printf(response, "username=\"%s\"", js->user->node); - g_string_append_printf(response, ",realm=\"%s\"", realm); - g_string_append_printf(response, ",nonce=\"%s\"", nonce); - g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); - g_string_append_printf(response, ",nc=00000001"); - g_string_append_printf(response, ",qop=auth"); - g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); - g_string_append_printf(response, ",response=%s", auth_resp); - g_string_append_printf(response, ",charset=utf-8"); + const char *ns = xmlnode_get_namespace(packet); - g_free(auth_resp); - g_free(cnonce); - - enc_out = purple_base64_encode((guchar *)response->str, response->len); - - purple_debug_misc("jabber", "decoded response (%" - G_GSIZE_FORMAT "): %s\n", - response->len, response->str); - - buf = g_strdup_printf("%s", enc_out); - - jabber_send_raw(js, buf, -1); - - g_free(buf); - - g_free(enc_out); - - g_string_free(response, TRUE); - } - } - - g_free(enc_in); - g_free(dec_in); - g_hash_table_destroy(parts); - } -#ifdef HAVE_CYRUS_SASL - else if (js->auth_type == JABBER_AUTH_CYRUS) { - char *enc_in = xmlnode_get_data(packet); - unsigned char *dec_in; - char *enc_out; - const char *c_out; - unsigned int clen; - gsize declen; - xmlnode *response; - - dec_in = purple_base64_decode(enc_in, &declen); - - js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, - NULL, &c_out, &clen); - g_free(enc_in); - g_free(dec_in); - if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { - gchar *tmp = g_strdup_printf(_("SASL error: %s"), - sasl_errdetail(js->sasl)); - purple_debug_error("jabber", "Error is %d : %s\n", - js->sasl_state, sasl_errdetail(js->sasl)); - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); - g_free(tmp); - return; - } else { - response = xmlnode_new("response"); - xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl"); - if (clen > 0) { - /* Cyrus SASL 2.1.22 appears to contain code to add the charset - * to the response for DIGEST-MD5 but there is no possibility - * it will be executed. - * - * My reading of the digestmd5 plugin indicates the username and - * realm are always encoded in UTF-8 (they seem to be the values - * we pass in), so we need to ensure charset=utf-8 is set. - */ - if (!purple_strequal(js->current_mech, "DIGEST-MD5") || - strstr(c_out, ",charset=")) - /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ - enc_out = purple_base64_encode((unsigned char*)c_out, clen); - else { - char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); - enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); - g_free(tmp); - } - - xmlnode_insert_data(response, enc_out, -1); - g_free(enc_out); - } - jabber_send(js, response); - xmlnode_free(response); - } - } -#endif -} - -void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) -{ - const char *ns = xmlnode_get_namespace(packet); -#ifdef HAVE_CYRUS_SASL - const void *x; -#endif - - if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + if (!purple_strequal(ns, NS_XMPP_SASL)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); return; } -#ifdef HAVE_CYRUS_SASL - /* The SASL docs say that if the client hasn't returned OK yet, we - * should try one more round against it - */ - if (js->sasl_state != SASL_OK) { - char *enc_in = xmlnode_get_data(packet); - unsigned char *dec_in = NULL; - const char *c_out; - unsigned int clen; - gsize declen = 0; + if (js->auth_mech && js->auth_mech->handle_challenge) { + xmlnode *response = NULL; + char *msg = NULL; + JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg); + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Invalid challenge from server")); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } - if(enc_in != NULL) - dec_in = purple_base64_decode(enc_in, &declen); + g_free(msg); + } else + purple_debug_warning("jabber", "Received unexpected (and unhandled) \n"); +} - js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); +void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) +{ + const char *ns = xmlnode_get_namespace(packet); - g_free(enc_in); - g_free(dec_in); + if (!purple_strequal(ns, NS_XMPP_SASL)) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server")); + return; + } + + if (js->auth_mech && js->auth_mech->handle_success) { + char *msg = NULL; + JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg); - if (js->sasl_state != SASL_OK) { - /* This should never happen! */ + if (state == JABBER_SASL_STATE_FAIL) { purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Invalid response from server")); - g_return_if_reached(); + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Invalid response from server")); + return; + } else if (state == JABBER_SASL_STATE_CONTINUE) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + msg ? msg : _("Server thinks authentication is complete, but client does not")); + return; } + + g_free(msg); } - /* If we've negotiated a security layer, we need to enable it */ - if (js->sasl) { - sasl_getprop(js->sasl, SASL_SSF, &x); - if (*(int *)x > 0) { - sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); - js->sasl_maxbuf = *(int *)x; - } - } -#endif /* * The stream will be reinitialized later in jabber_recv_cb_ssl() or @@ -1095,31 +479,23 @@ void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; - char *msg; + char *msg = NULL; -#ifdef HAVE_CYRUS_SASL - if(js->auth_fail_count++ < 5) { - if (js->current_mech && *js->current_mech) { - char *pos; - if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { - g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); - } - /* Remove space which separated this mech from the next */ - if ((js->sasl_mechs->str)[0] == ' ') { - g_string_erase(js->sasl_mechs, 0, 1); - } - } - if (*js->sasl_mechs->str) { - /* If we have remaining mechs to try, do so */ - sasl_dispose(&js->sasl); + if (js->auth_mech && js->auth_mech->handle_failure) { + xmlnode *stanza = NULL; + JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg); - jabber_auth_start_cyrus(js); + if (state != JABBER_SASL_STATE_FAIL && stanza) { + jabber_send(js, stanza); + xmlnode_free(stanza); return; } } -#endif - msg = jabber_parse_error(js, packet, &reason); - if(!msg) { + + if (!msg) + msg = jabber_parse_error(js, packet, &reason); + + if (!msg) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Invalid response from server")); @@ -1128,3 +504,39 @@ g_free(msg); } } + +static gint compare_mech(gconstpointer a, gconstpointer b) +{ + const JabberSaslMech *mech_a = a; + const JabberSaslMech *mech_b = b; + + /* higher priority comes *before* lower priority in the list */ + if (mech_a->priority > mech_b->priority) + return -1; + else if (mech_a->priority < mech_b->priority) + return 1; + /* This really shouldn't happen */ + return 0; +} + +void jabber_auth_init(void) +{ + JabberSaslMech **tmp; + gint count, i; + + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech); + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech); +#ifdef HAVE_CYRUS_SASL + auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech); +#endif + + tmp = jabber_auth_get_scram_mechs(&count); + for (i = 0; i < count; ++i) + auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech); +} + +void jabber_auth_uninit(void) +{ + g_slist_free(auth_mechs); + auth_mechs = NULL; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth.h --- a/libpurple/protocols/jabber/auth.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/auth.h Thu Dec 17 16:50:12 2009 +0900 @@ -24,9 +24,27 @@ #ifndef PURPLE_JABBER_AUTH_H_ #define PURPLE_JABBER_AUTH_H_ +typedef struct _JabberSaslMech JabberSaslMech; + #include "jabber.h" #include "xmlnode.h" +typedef enum { + JABBER_SASL_STATE_FAIL = -1, /* Abort, Retry, Fail? */ + JABBER_SASL_STATE_OK = 0, /* Hooray! */ + JABBER_SASL_STATE_CONTINUE = 1 /* More authentication required */ +} JabberSaslState; + +struct _JabberSaslMech { + gint8 priority; /* Higher priority will be tried before lower priority */ + const gchar *name; + JabberSaslState (*start)(JabberStream *js, xmlnode *mechanisms, xmlnode **reply, char **msg); + JabberSaslState (*handle_challenge)(JabberStream *js, xmlnode *packet, xmlnode **reply, char **msg); + JabberSaslState (*handle_success)(JabberStream *js, xmlnode *packet, char **msg); + JabberSaslState (*handle_failure)(JabberStream *js, xmlnode *packet, xmlnode **reply, char **msg); + void (*dispose)(JabberStream *js); +}; + gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet); void jabber_auth_start(JabberStream *js, xmlnode *packet); void jabber_auth_start_old(JabberStream *js); @@ -34,4 +52,14 @@ void jabber_auth_handle_success(JabberStream *js, xmlnode *packet); void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet); +JabberSaslMech *jabber_auth_get_plain_mech(void); +JabberSaslMech *jabber_auth_get_digest_md5_mech(void); +JabberSaslMech **jabber_auth_get_scram_mechs(gint *count); +#ifdef HAVE_CYRUS_SASL +JabberSaslMech *jabber_auth_get_cyrus_mech(void); +#endif + +void jabber_auth_init(void); +void jabber_auth_uninit(void); + #endif /* PURPLE_JABBER_AUTH_H_ */ diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth_cyrus.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_cyrus.c Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,564 @@ +/* + * purple - Jabber Protocol Plugin + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ +#include "internal.h" +#include "core.h" +#include "debug.h" +#include "request.h" + +#include "auth.h" +#include "jabber.h" + +static JabberSaslState jabber_auth_start_cyrus(JabberStream *js, xmlnode **reply, + char **error); +static void jabber_sasl_build_callbacks(JabberStream *); + +static void disallow_plaintext_auth(PurpleAccount *account) +{ + purple_connection_error_reason(purple_account_get_connection(account), + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); +} + +static void start_cyrus_wrapper(JabberStream *js) +{ + char *error = NULL; + xmlnode *response = NULL; + JabberSaslState state = jabber_auth_start_cyrus(js, &response, &error); + + if (state == JABBER_SASL_STATE_FAIL) { + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + error); + g_free(error); + } else if (response) { + jabber_send(js, response); + xmlnode_free(response); + } +} + + +/* Callbacks for Cyrus SASL */ + +static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result) +{ + JabberStream *js = ctx; + + if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM; + + *result = js->user->domain; + + return SASL_OK; +} + +static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len) +{ + JabberStream *js = ctx; + + switch(id) { + case SASL_CB_AUTHNAME: + *res = js->user->node; + break; + case SASL_CB_USER: + *res = ""; + break; + default: + return SASL_BADPARAM; + } + if (len) *len = strlen((char *)*res); + return SASL_OK; +} + +static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret) +{ + JabberStream *js = ctx; + PurpleAccount *account; + const char *pw; + size_t len; + static sasl_secret_t *x = NULL; + + account = purple_connection_get_account(js->gc); + pw = purple_account_get_password(account); + + if (!conn || !secret || id != SASL_CB_PASS) + return SASL_BADPARAM; + + len = strlen(pw); + x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len); + + if (!x) + return SASL_NOMEM; + + x->len = len; + strcpy((char*)x->data, pw); + + *secret = x; + return SASL_OK; +} + +static void allow_cyrus_plaintext_auth(PurpleAccount *account) +{ + PurpleConnection *gc; + JabberStream *js; + + gc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(gc); + + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); + + start_cyrus_wrapper(js); +} + +static void auth_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *account; + JabberStream *js; + const char *entry; + gboolean remember; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + entry = purple_request_fields_get_string(fields, "password"); + remember = purple_request_fields_get_bool(fields, "remember"); + + if (!entry || !*entry) + { + purple_notify_error(account, NULL, _("Password is required to sign on."), NULL); + return; + } + + if (remember) + purple_account_set_remember_password(account, TRUE); + + purple_account_set_password(account, entry); + + /* Rebuild our callbacks as we now have a password to offer */ + jabber_sasl_build_callbacks(js); + + /* Restart our negotiation */ + start_cyrus_wrapper(js); +} + +static void +auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) +{ + PurpleAccount *account; + JabberStream *js; + + /* The password prompt dialog doesn't get disposed if the account disconnects */ + if (!PURPLE_CONNECTION_IS_VALID(gc)) + return; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + /* Disable the account as the user has canceled connecting */ + purple_account_set_enabled(account, purple_core_get_ui(), FALSE); +} + +static JabberSaslState +jabber_auth_start_cyrus(JabberStream *js, xmlnode **reply, char **error) +{ + PurpleAccount *account; + const char *clientout = NULL; + char *enc_out; + unsigned coutlen = 0; + sasl_security_properties_t secprops; + gboolean again; + gboolean plaintext = TRUE; + + /* Set up security properties and options */ + secprops.min_ssf = 0; + secprops.security_flags = SASL_SEC_NOANONYMOUS; + + account = purple_connection_get_account(js->gc); + + if (!jabber_stream_is_ssl(js)) { + secprops.max_ssf = -1; + secprops.maxbufsize = 4096; + plaintext = purple_account_get_bool(account, "auth_plain_in_clear", FALSE); + if (!plaintext) + secprops.security_flags |= SASL_SEC_NOPLAINTEXT; + } else { + secprops.max_ssf = 0; + secprops.maxbufsize = 0; + plaintext = TRUE; + } + secprops.property_names = 0; + secprops.property_values = 0; + + do { + again = FALSE; + + js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl); + if (js->sasl_state==SASL_OK) { + sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops); + purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str); + js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech); + } + switch (js->sasl_state) { + /* Success */ + case SASL_OK: + case SASL_CONTINUE: + break; + case SASL_NOMECH: + /* No mechanisms have offered to help */ + + /* Firstly, if we don't have a password try + * to get one + */ + + if (!purple_account_get_password(account)) { + purple_account_request_password(account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); + return JABBER_SASL_STATE_CONTINUE; + + /* If we've got a password, but aren't sending + * it in plaintext, see if we can turn on + * plaintext auth + */ + } else if (!plaintext) { + char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), + purple_account_get_username(account)); + purple_request_yes_no(js->gc, _("Plaintext Authentication"), + _("Plaintext Authentication"), + msg, + 1, account, NULL, NULL, account, + allow_cyrus_plaintext_auth, + disallow_plaintext_auth); + g_free(msg); + return JABBER_SASL_STATE_CONTINUE; + + } else { + /* We have no mechs which can work. + * Try falling back on the old jabber:iq:auth method. We get here if the server supports + * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of + * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect + * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often + * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. + * + * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, + * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. + * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers + * which would connect without issue otherwise. -evands + */ + js->auth_mech = NULL; + jabber_auth_start_old(js); + return JABBER_SASL_STATE_CONTINUE; + } + /* not reached */ + break; + + /* Fatal errors. Give up and go home */ + case SASL_BADPARAM: + case SASL_NOMEM: + break; + + /* For everything else, fail the mechanism and try again */ + default: + purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state); + + /* + * DAA: is this right? + * The manpage says that "mech" will contain the chosen mechanism on success. + * Presumably, if we get here that isn't the case and we shouldn't try again? + * I suspect that this never happens. + */ + /* + * SXW: Yes, this is right. What this handles is the situation where a + * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be + * due to mechanism specific issues, so we want to try one of the other + * supported mechanisms. This code handles that case + */ + if (js->current_mech && *js->current_mech) { + char *pos; + if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { + g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); + } + /* Remove space which separated this mech from the next */ + if ((js->sasl_mechs->str)[0] == ' ') { + g_string_erase(js->sasl_mechs, 0, 1); + } + again = TRUE; + } + + sasl_dispose(&js->sasl); + } + } while (again); + + if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) { + xmlnode *auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + xmlnode_set_attrib(auth, "mechanism", js->current_mech); + + xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); + xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); + + if (clientout) { + if (coutlen == 0) { + xmlnode_insert_data(auth, "=", -1); + } else { + enc_out = purple_base64_encode((unsigned char*)clientout, coutlen); + xmlnode_insert_data(auth, enc_out, -1); + g_free(enc_out); + } + } + + *reply = auth; + return JABBER_SASL_STATE_CONTINUE; + } else { + *error = g_strdup(_("SASL authentication failed")); + return JABBER_SASL_STATE_FAIL; + } +} + +static int +jabber_sasl_cb_log(void *context, int level, const char *message) +{ + if(level <= SASL_LOG_TRACE) + purple_debug_info("sasl", "%s\n", message); + + return SASL_OK; +} + +static void +jabber_sasl_build_callbacks(JabberStream *js) +{ + PurpleAccount *account; + int id; + + /* Set up our callbacks structure */ + if (js->sasl_cb == NULL) + js->sasl_cb = g_new0(sasl_callback_t,6); + + id = 0; + js->sasl_cb[id].id = SASL_CB_GETREALM; + js->sasl_cb[id].proc = jabber_sasl_cb_realm; + js->sasl_cb[id].context = (void *)js; + id++; + + js->sasl_cb[id].id = SASL_CB_AUTHNAME; + js->sasl_cb[id].proc = jabber_sasl_cb_simple; + js->sasl_cb[id].context = (void *)js; + id++; + + js->sasl_cb[id].id = SASL_CB_USER; + js->sasl_cb[id].proc = jabber_sasl_cb_simple; + js->sasl_cb[id].context = (void *)js; + id++; + + account = purple_connection_get_account(js->gc); + if (purple_account_get_password(account) != NULL ) { + js->sasl_cb[id].id = SASL_CB_PASS; + js->sasl_cb[id].proc = jabber_sasl_cb_secret; + js->sasl_cb[id].context = (void *)js; + id++; + } + + js->sasl_cb[id].id = SASL_CB_LOG; + js->sasl_cb[id].proc = jabber_sasl_cb_log; + js->sasl_cb[id].context = (void*)js; + id++; + + js->sasl_cb[id].id = SASL_CB_LIST_END; +} + +static JabberSaslState +jabber_cyrus_start(JabberStream *js, xmlnode *mechanisms, + xmlnode **reply, char **error) +{ + xmlnode *mechnode; + + js->sasl_mechs = g_string_new(""); + + for(mechnode = xmlnode_get_child(mechanisms, "mechanism"); mechnode; + mechnode = xmlnode_get_next_twin(mechnode)) + { + char *mech_name = xmlnode_get_data(mechnode); + + if (!mech_name || !*mech_name) { + g_free(mech_name); + continue; + } + + /* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not + * support it and including it gives a false fall-back to other mechs offerred, + * leading to incorrect error handling. + */ + if (g_str_equal(mech_name, "X-GOOGLE-TOKEN")) { + g_free(mech_name); + continue; + } + + g_string_append(js->sasl_mechs, mech_name); + g_string_append_c(js->sasl_mechs, ' '); + g_free(mech_name); + } + + jabber_sasl_build_callbacks(js); + return jabber_auth_start_cyrus(js, reply, error); +} + +static JabberSaslState +jabber_cyrus_handle_challenge(JabberStream *js, xmlnode *packet, + xmlnode **reply, char **error) +{ + char *enc_in = xmlnode_get_data(packet); + unsigned char *dec_in; + char *enc_out; + const char *c_out; + unsigned int clen; + gsize declen; + + dec_in = purple_base64_decode(enc_in, &declen); + + js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, + NULL, &c_out, &clen); + g_free(enc_in); + g_free(dec_in); + if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { + gchar *tmp = g_strdup_printf(_("SASL error: %s"), + sasl_errdetail(js->sasl)); + purple_debug_error("jabber", "Error is %d : %s\n", + js->sasl_state, sasl_errdetail(js->sasl)); + *error = tmp; + return JABBER_SASL_STATE_FAIL; + } else { + xmlnode *response = xmlnode_new("response"); + xmlnode_set_namespace(response, NS_XMPP_SASL); + if (clen > 0) { + /* Cyrus SASL 2.1.22 appears to contain code to add the charset + * to the response for DIGEST-MD5 but there is no possibility + * it will be executed. + * + * My reading of the digestmd5 plugin indicates the username and + * realm are always encoded in UTF-8 (they seem to be the values + * we pass in), so we need to ensure charset=utf-8 is set. + */ + if (!purple_strequal(js->current_mech, "DIGEST-MD5") || + strstr(c_out, ",charset=")) + /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ + enc_out = purple_base64_encode((unsigned char*)c_out, clen); + else { + char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); + enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); + g_free(tmp); + } + + xmlnode_insert_data(response, enc_out, -1); + g_free(enc_out); + } + + *reply = response; + return JABBER_SASL_STATE_CONTINUE; + } +} + +static JabberSaslState +jabber_cyrus_handle_success(JabberStream *js, xmlnode *packet, + char **error) +{ + const void *x; + + /* The SASL docs say that if the client hasn't returned OK yet, we + * should try one more round against it + */ + if (js->sasl_state != SASL_OK) { + char *enc_in = xmlnode_get_data(packet); + unsigned char *dec_in = NULL; + const char *c_out; + unsigned int clen; + gsize declen = 0; + + if(enc_in != NULL) + dec_in = purple_base64_decode(enc_in, &declen); + + js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); + + g_free(enc_in); + g_free(dec_in); + + if (js->sasl_state != SASL_OK) { + /* This should never happen! */ + *error = g_strdup(_("Invalid response from server")); + g_return_val_if_reached(JABBER_SASL_STATE_FAIL); + } + } + + /* If we've negotiated a security layer, we need to enable it */ + if (js->sasl) { + sasl_getprop(js->sasl, SASL_SSF, &x); + if (*(int *)x > 0) { + sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); + js->sasl_maxbuf = *(int *)x; + } + } + + return JABBER_SASL_STATE_OK; +} + +static JabberSaslState +jabber_cyrus_handle_failure(JabberStream *js, xmlnode *packet, + xmlnode **reply, char **error) +{ + if (js->auth_fail_count++ < 5) { + if (js->current_mech && *js->current_mech) { + char *pos; + if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { + g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); + } + /* Remove space which separated this mech from the next */ + if ((js->sasl_mechs->str)[0] == ' ') { + g_string_erase(js->sasl_mechs, 0, 1); + } + } + if (*js->sasl_mechs->str) { + /* If we have remaining mechs to try, do so */ + sasl_dispose(&js->sasl); + + return jabber_auth_start_cyrus(js, reply, error); + } + } + + /* Nothing to send */ + return JABBER_SASL_STATE_FAIL; +} + +static JabberSaslMech cyrus_mech = { + 100, /* priority */ + "*", /* name; Cyrus provides a bunch of mechanisms, so use an invalid + * mechanism name (per rfc4422 3.1). */ + jabber_cyrus_start, + jabber_cyrus_handle_challenge, + jabber_cyrus_handle_success, + jabber_cyrus_handle_failure, + NULL, +}; + +JabberSaslMech *jabber_auth_get_cyrus_mech(void) +{ + return &cyrus_mech; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth_digest_md5.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_digest_md5.c Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,292 @@ +/* + * purple - Jabber Protocol Plugin + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ +#include "internal.h" + +#include "debug.h" +#include "cipher.h" +#include "util.h" +#include "xmlnode.h" + +#include "auth.h" +#include "jabber.h" + +static JabberSaslState +digest_md5_start(JabberStream *js, xmlnode *packet, xmlnode **response, + char **error) +{ + xmlnode *auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5"); + + *response = auth; + return JABBER_SASL_STATE_CONTINUE; +} + +/* Parts of this algorithm are inspired by stuff in libgsasl */ +static GHashTable* parse_challenge(const char *challenge) +{ + const char *token_start, *val_start, *val_end, *cur; + GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + cur = challenge; + while(*cur != '\0') { + /* Find the end of the token */ + gboolean in_quotes = FALSE; + char *name, *value = NULL; + token_start = cur; + while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) { + if (*cur == '"') + in_quotes = !in_quotes; + cur++; + } + + /* Find start of value. */ + val_start = strchr(token_start, '='); + if (val_start == NULL || val_start > cur) + val_start = cur; + + if (token_start != val_start) { + name = g_strndup(token_start, val_start - token_start); + + if (val_start != cur) { + val_start++; + while (val_start != cur && (*val_start == ' ' || *val_start == '\t' + || *val_start == '\r' || *val_start == '\n' + || *val_start == '"')) + val_start++; + + val_end = cur; + while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t' + || *val_end == '\r' || *val_end == '\n' + || *val_end == '"' || *val_end == '\0')) + val_end--; + + if (val_start != val_end) + value = g_strndup(val_start, val_end - val_start + 1); + } + + g_hash_table_replace(ret, name, value); + } + + /* Find the start of the next token, if there is one */ + if (*cur != '\0') { + cur++; + while (*cur == ' ' || *cur == ',' || *cur == '\t' + || *cur == '\r' || *cur == '\n') + cur++; + } + } + + return ret; +} + +static char * +generate_response_value(JabberID *jid, const char *passwd, const char *nonce, + const char *cnonce, const char *a2, const char *realm) +{ + PurpleCipher *cipher; + PurpleCipherContext *context; + guchar result[16]; + size_t a1len; + + gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z; + + if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8", + NULL, NULL, NULL)) == NULL) { + convnode = g_strdup(jid->node); + } + if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1", + "utf-8", NULL, NULL, NULL)) == NULL)) { + convpasswd = g_strdup(passwd); + } + + cipher = purple_ciphers_find_cipher("md5"); + context = purple_cipher_context_new(cipher, NULL); + + x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : ""); + purple_cipher_context_append(context, (const guchar *)x, strlen(x)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce); + a1len = strlen(a1); + g_memmove(a1, result, 16); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)a1, a1len); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + ha1 = purple_base16_encode(result, 16); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)a2, strlen(a2)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + + ha2 = purple_base16_encode(result, 16); + + kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2); + + purple_cipher_context_reset(context, NULL); + purple_cipher_context_append(context, (const guchar *)kd, strlen(kd)); + purple_cipher_context_digest(context, sizeof(result), result, NULL); + purple_cipher_context_destroy(context); + + z = purple_base16_encode(result, 16); + + g_free(convnode); + g_free(convpasswd); + g_free(x); + g_free(a1); + g_free(ha1); + g_free(ha2); + g_free(kd); + + return z; +} + +static JabberSaslState +digest_md5_handle_challenge(JabberStream *js, xmlnode *packet, + xmlnode **response, char **msg) +{ + xmlnode *reply = NULL; + char *enc_in = xmlnode_get_data(packet); + char *dec_in; + char *enc_out; + GHashTable *parts; + JabberSaslState state = JABBER_SASL_STATE_CONTINUE; + + if (!enc_in) { + *msg = g_strdup(_("Invalid response from server")); + return JABBER_SASL_STATE_FAIL; + } + + dec_in = (char *)purple_base64_decode(enc_in, NULL); + purple_debug_misc("jabber", "decoded challenge (%" + G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); + + parts = parse_challenge(dec_in); + + if (g_hash_table_lookup(parts, "rspauth")) { + char *rspauth = g_hash_table_lookup(parts, "rspauth"); + + if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) { + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + } else { + *msg = g_strdup(_("Invalid challenge from server")); + state = JABBER_SASL_STATE_FAIL; + } + g_free(js->expected_rspauth); + js->expected_rspauth = NULL; + } else { + /* assemble a response, and send it */ + /* see RFC 2831 */ + char *realm; + char *nonce; + + /* Make sure the auth string contains everything that should be there. + This isn't everything in RFC2831, but it is what we need. */ + + nonce = g_hash_table_lookup(parts, "nonce"); + + /* we're actually supposed to prompt the user for a realm if + * the server doesn't send one, but that really complicates things, + * so i'm not gonna worry about it until is poses a problem to + * someone, or I get really bored */ + realm = g_hash_table_lookup(parts, "realm"); + if(!realm) + realm = js->user->domain; + + if (nonce == NULL || realm == NULL) { + *msg = g_strdup(_("Invalid challenge from server")); + state = JABBER_SASL_STATE_FAIL; + } else { + GString *response = g_string_new(""); + char *a2; + char *auth_resp; + char *cnonce; + + cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), + g_random_int()); + + a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); + auth_resp = generate_response_value(js->user, + purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); + g_free(a2); + + a2 = g_strdup_printf(":xmpp/%s", realm); + js->expected_rspauth = generate_response_value(js->user, + purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); + g_free(a2); + + g_string_append_printf(response, "username=\"%s\"", js->user->node); + g_string_append_printf(response, ",realm=\"%s\"", realm); + g_string_append_printf(response, ",nonce=\"%s\"", nonce); + g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); + g_string_append_printf(response, ",nc=00000001"); + g_string_append_printf(response, ",qop=auth"); + g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); + g_string_append_printf(response, ",response=%s", auth_resp); + g_string_append_printf(response, ",charset=utf-8"); + + g_free(auth_resp); + g_free(cnonce); + + enc_out = purple_base64_encode((guchar *)response->str, response->len); + + purple_debug_misc("jabber", "decoded response (%" + G_GSIZE_FORMAT "): %s\n", + response->len, response->str); + + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + xmlnode_insert_data(reply, enc_out, -1); + + g_free(enc_out); + + g_string_free(response, TRUE); + } + } + + g_free(enc_in); + g_free(dec_in); + g_hash_table_destroy(parts); + + *response = reply; + return state; +} + +static JabberSaslMech digest_md5_mech = { + 10, /* priority */ + "DIGEST-MD5", /* name */ + digest_md5_start, + digest_md5_handle_challenge, + NULL, /* handle_success */ + NULL, /* handle_failure */ + NULL /* handle_dispose */ +}; + +JabberSaslMech *jabber_auth_get_digest_md5_mech(void) +{ + return &digest_md5_mech; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth_plain.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_plain.c Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,119 @@ +/* + * purple - Jabber Protocol Plugin + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ +#include "internal.h" + +#include "account.h" +#include "debug.h" +#include "request.h" +#include "util.h" +#include "xmlnode.h" + +#include "jabber.h" +#include "auth.h" + +static xmlnode *finish_plaintext_authentication(JabberStream *js) +{ + xmlnode *auth; + GString *response; + gchar *enc_out; + + auth = xmlnode_new("auth"); + xmlnode_set_namespace(auth, NS_XMPP_SASL); + + xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); + xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); + + response = g_string_new(""); + response = g_string_append_len(response, "\0", 1); + response = g_string_append(response, js->user->node); + response = g_string_append_len(response, "\0", 1); + response = g_string_append(response, + purple_connection_get_password(js->gc)); + + enc_out = purple_base64_encode((guchar *)response->str, response->len); + + xmlnode_set_attrib(auth, "mechanism", "PLAIN"); + xmlnode_insert_data(auth, enc_out, -1); + g_free(enc_out); + g_string_free(response, TRUE); + + return auth; +} + +static void allow_plaintext_auth(PurpleAccount *account) +{ + PurpleConnection *gc = purple_account_get_connection(account); + JabberStream *js = purple_connection_get_protocol_data(gc); + xmlnode *response; + + purple_account_set_bool(account, "auth_plain_in_clear", TRUE); + + response = finish_plaintext_authentication(js); + jabber_send(js, response); + xmlnode_free(response); +} + +static void disallow_plaintext_auth(PurpleAccount *account) +{ + purple_connection_error_reason(purple_account_get_connection(account), + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); +} + +static JabberSaslState +jabber_plain_start(JabberStream *js, xmlnode *packet, xmlnode **response, char **error) +{ + PurpleAccount *account = purple_connection_get_account(js->gc); + char *msg; + + if (jabber_stream_is_ssl(js) || purple_account_get_bool(account, "auth_plain_in_clear", FALSE)) { + *response = finish_plaintext_authentication(js); + return JABBER_SASL_STATE_OK; + } + + msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), + purple_account_get_username(account)); + purple_request_yes_no(js->gc, _("Plaintext Authentication"), + _("Plaintext Authentication"), + msg, + 1, + account, NULL, NULL, + account, allow_plaintext_auth, disallow_plaintext_auth); + g_free(msg); + return JABBER_SASL_STATE_CONTINUE; +} + +static JabberSaslMech plain_mech = { + 0, /* priority */ + "PLAIN", /* name */ + jabber_plain_start, + NULL, /* handle_challenge */ + NULL, /* handle_success */ + NULL, /* handle_failure */ + NULL /* dispose */ +}; + +JabberSaslMech *jabber_auth_get_plain_mech(void) +{ + return &plain_mech; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth_scram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_scram.c Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,577 @@ +/* + * purple - Jabber Protocol Plugin + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ +#include "internal.h" + +#include "auth.h" +#include "auth_scram.h" + +#include "cipher.h" +#include "debug.h" + +static const JabberScramHash hashes[] = { + { "-SHA-1", "sha1", 20 }, +}; + +static const JabberScramHash *mech_to_hash(const char *mech) +{ + int i; + + g_return_val_if_fail(mech != NULL && *mech != '\0', NULL); + + for (i = 0; i < G_N_ELEMENTS(hashes); ++i) { + if (strstr(mech, hashes[i].mech_substr)) + return &(hashes[i]); + } + + purple_debug_error("jabber", "Unknown SCRAM mechanism %s\n", mech); + g_return_val_if_reached(NULL); +} + +guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str, + GString *salt, guint iterations) +{ + PurpleCipherContext *context; + guchar *result; + guint i; + guchar *prev, *tmp; + + g_return_val_if_fail(hash != NULL, NULL); + g_return_val_if_fail(str != NULL && str->len > 0, NULL); + g_return_val_if_fail(salt != NULL && salt->len > 0, NULL); + g_return_val_if_fail(iterations > 0, NULL); + + prev = g_new0(guint8, hash->size); + tmp = g_new0(guint8, hash->size); + result = g_new0(guint8, hash->size); + + context = purple_cipher_context_new_by_name("hmac", NULL); + + /* Append INT(1), a four-octet encoding of the integer 1, most significant + * octet first. */ + g_string_append_len(salt, "\0\0\0\1", 4); + + /* Compute U0 */ + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, (guchar *)str->str, str->len); + purple_cipher_context_append(context, (guchar *)salt->str, salt->len); + purple_cipher_context_digest(context, hash->size, result, NULL); + + memcpy(prev, result, hash->size); + + /* Compute U1...Ui */ + for (i = 1; i < iterations; ++i) { + guint j; + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, (guchar *)str->str, str->len); + purple_cipher_context_append(context, prev, hash->size); + purple_cipher_context_digest(context, hash->size, tmp, NULL); + + for (j = 0; j < hash->size; ++j) + result[j] ^= tmp[j]; + + memcpy(prev, tmp, hash->size); + } + + purple_cipher_context_destroy(context); + g_free(tmp); + g_free(prev); + return result; +} + +/* + * Helper functions for doing the SCRAM calculations. The first argument + * is the hash algorithm. All buffers must be of the appropriate size + * according to the JabberScramHash. + * + * "str" is a NULL-terminated string for hmac(). + * + * Needless to say, these are fragile. + */ +static void +hmac(const JabberScramHash *hash, guchar *out, const guchar *key, const gchar *str) +{ + PurpleCipherContext *context; + + context = purple_cipher_context_new_by_name("hmac", NULL); + purple_cipher_context_set_option(context, "hash", (gpointer)hash->name); + purple_cipher_context_set_key_with_len(context, key, hash->size); + purple_cipher_context_append(context, (guchar *)str, strlen(str)); + purple_cipher_context_digest(context, hash->size, out, NULL); + purple_cipher_context_destroy(context); +} + +static void +hash(const JabberScramHash *hash, guchar *out, const guchar *data) +{ + PurpleCipherContext *context; + + context = purple_cipher_context_new_by_name(hash->name, NULL); + purple_cipher_context_append(context, data, hash->size); + purple_cipher_context_digest(context, hash->size, out, NULL); + purple_cipher_context_destroy(context); +} + +gboolean +jabber_scram_calc_proofs(JabberScramData *data, GString *salt, guint iterations) +{ + guint hash_len = data->hash->size; + guint i; + + GString *pass = g_string_new(data->password); + + guchar *salted_password; + guchar *client_key, *stored_key, *client_signature, *server_key; + + client_key = g_new0(guchar, hash_len); + stored_key = g_new0(guchar, hash_len); + client_signature = g_new0(guchar, hash_len); + server_key = g_new0(guchar, hash_len); + + data->client_proof = g_string_sized_new(hash_len); + data->client_proof->len = hash_len; + data->server_signature = g_string_sized_new(hash_len); + data->server_signature->len = hash_len; + + salted_password = jabber_scram_hi(data->hash, pass, salt, iterations); + + memset(pass->str, 0, pass->allocated_len); + g_string_free(pass, TRUE); + + if (!salted_password) + return FALSE; + + /* client_key = HMAC(salted_password, "Client Key") */ + hmac(data->hash, client_key, salted_password, "Client Key"); + /* server_key = HMAC(salted_password, "Server Key") */ + hmac(data->hash, server_key, salted_password, "Server Key"); + g_free(salted_password); + + /* stored_key = HASH(client_key) */ + hash(data->hash, stored_key, client_key); + + /* client_signature = HMAC(stored_key, auth_message) */ + hmac(data->hash, client_signature, stored_key, data->auth_message->str); + /* server_signature = HMAC(server_key, auth_message) */ + hmac(data->hash, (guchar *)data->server_signature->str, server_key, data->auth_message->str); + + /* client_proof = client_key XOR client_signature */ + for (i = 0; i < hash_len; ++i) + data->client_proof->str[i] = client_key[i] ^ client_signature[i]; + + g_free(server_key); + g_free(client_signature); + g_free(stored_key); + g_free(client_key); + + return TRUE; +} + +static gboolean +parse_server_step1(JabberScramData *data, const char *challenge, + gchar **out_nonce, GString **out_salt, guint *out_iterations) +{ + char **tokens; + char *token, *decoded, *tmp; + gsize len; + char *nonce = NULL; + GString *salt = NULL; + guint iterations; + + tokens = g_strsplit(challenge, ",", -1); + if (tokens == NULL) + return FALSE; + + token = tokens[0]; + if (token[0] != 'r' || token[1] != '=') + goto err; + + /* Ensure that the first cnonce_len bytes of the nonce are the original + * cnonce we sent to the server. + */ + if (0 != strncmp(data->cnonce, token + 2, strlen(data->cnonce))) + goto err; + + nonce = g_strdup(token + 2); + + /* The Salt, base64-encoded */ + token = tokens[1]; + if (token[0] != 's' || token[1] != '=') + goto err; + + decoded = (gchar *)purple_base64_decode(token + 2, &len); + if (!decoded || *decoded == '\0') { + g_free(decoded); + goto err; + } + salt = g_string_new_len(decoded, len); + g_free(decoded); + + /* The iteration count */ + token = tokens[2]; + if (token[0] != 'i' || token[1] != '=' || token[2] == '\0') + goto err; + + /* Validate the string */ + for (tmp = token + 2; *tmp; ++tmp) + if (!g_ascii_isdigit(*tmp)) + goto err; + + iterations = strtoul(token + 2, NULL, 10); + + g_strfreev(tokens); + *out_nonce = nonce; + *out_salt = salt; + *out_iterations = iterations; + return TRUE; + +err: + g_free(nonce); + if (salt) + g_string_free(salt, TRUE); + g_strfreev(tokens); + return FALSE; +} + +static gboolean +parse_server_step2(JabberScramData *data, const char *challenge, gchar **out_verifier) +{ + char **tokens; + char *token; + + tokens = g_strsplit(challenge, ",", -1); + if (tokens == NULL) + return FALSE; + + token = tokens[0]; + if (token[0] != 'v' || token[1] != '=' || token[2] == '\0') { + g_strfreev(tokens); + return FALSE; + } + + *out_verifier = g_strdup(token + 2); + g_strfreev(tokens); + return TRUE; +} + +gboolean +jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out) +{ + gboolean ret; + + g_return_val_if_fail(data != NULL, FALSE); + + g_string_append_c(data->auth_message, ','); + g_string_append(data->auth_message, in); + + if (data->step == 1) { + gchar *nonce, *proof; + GString *salt; + guint iterations; + + ret = parse_server_step1(data, in, &nonce, &salt, &iterations); + if (!ret) + return FALSE; + + g_string_append_c(data->auth_message, ','); + + /* "biws" is the base64 encoding of "n,,". I promise. */ + g_string_append_printf(data->auth_message, "c=%s,r=%s", "biws", nonce); +#ifdef CHANNEL_BINDING +#error fix this +#endif + + ret = jabber_scram_calc_proofs(data, salt, iterations); + if (!ret) + return FALSE; + + proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len); + *out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof); + g_free(proof); + } else if (data->step == 2) { + gchar *server_sig, *enc_server_sig; + gsize len; + + ret = parse_server_step2(data, in, &enc_server_sig); + if (!ret) + return FALSE; + + server_sig = (gchar *)purple_base64_decode(enc_server_sig, &len); + g_free(enc_server_sig); + + if (server_sig == NULL || len != data->server_signature->len) { + g_free(server_sig); + return FALSE; + } + + if (0 != memcmp(server_sig, data->server_signature->str, len)) { + g_free(server_sig); + return FALSE; + } + g_free(server_sig); + + *out = NULL; + } else { + purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step); + return FALSE; + } + + return TRUE; +} + +static gchar *escape_username(const gchar *in) +{ + gchar *tmp, *tmp2; + + tmp = purple_strreplace(in, "=", "=3D"); + tmp2 = purple_strreplace(tmp, ",", "=2D"); + g_free(tmp); + return tmp2; +} + +static JabberSaslState +scram_start(JabberStream *js, xmlnode *mechanisms, xmlnode **out, char **error) +{ + xmlnode *reply; + JabberScramData *data; + guint64 cnonce; +#ifdef CHANNEL_BINDING + gboolean binding_supported = TRUE; +#endif + gchar *dec_out, *enc_out; + gchar *prepped_node, *tmp; + gchar *prepped_pass; + + prepped_node = jabber_saslprep(js->user->node); + if (!prepped_node) { + *error = g_strdup(_("Unable to canonicalize username")); + return JABBER_SASL_STATE_FAIL; + } + + tmp = escape_username(prepped_node); + g_free(prepped_node); + prepped_node = tmp; + + prepped_pass = jabber_saslprep(purple_connection_get_password(js->gc)); + if (!prepped_pass) { + g_free(prepped_node); + *error = g_strdup(_("Unable to canonicalize password")); + return JABBER_SASL_STATE_FAIL; + } + + data = js->auth_mech_data = g_new0(JabberScramData, 1); + data->hash = mech_to_hash(js->auth_mech->name); + data->password = prepped_pass; + +#ifdef CHANNEL_BINDING + if (strstr(js->auth_mech_name, "-PLUS")) + data->channel_binding = TRUE; +#endif + cnonce = ((guint64)g_random_int() << 32) | g_random_int(); + data->cnonce = purple_base64_encode((guchar *)&cnonce, sizeof(cnonce)); + + data->auth_message = g_string_new(NULL); + g_string_printf(data->auth_message, "n=%s,r=%s", + prepped_node, data->cnonce); + g_free(prepped_node); + + data->step = 1; + + reply = xmlnode_new("auth"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + xmlnode_set_attrib(reply, "mechanism", js->auth_mech->name); + + /* TODO: Channel binding */ + dec_out = g_strdup_printf("%c,,%s", 'n', data->auth_message->str); + enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); + purple_debug_misc("jabber", "initial SCRAM message '%s'\n", dec_out); + + xmlnode_insert_data(reply, enc_out, -1); + + g_free(enc_out); + g_free(dec_out); + + *out = reply; + return JABBER_SASL_STATE_CONTINUE; +} + +static JabberSaslState +scram_handle_challenge(JabberStream *js, xmlnode *challenge, xmlnode **out, char **error) +{ + JabberScramData *data = js->auth_mech_data; + xmlnode *reply; + gchar *enc_in, *dec_in; + gchar *enc_out = NULL, *dec_out = NULL; + gsize len; + JabberSaslState state = JABBER_SASL_STATE_FAIL; + + enc_in = xmlnode_get_data(challenge); + if (!enc_in || *enc_in == '\0') { + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Invalid challenge from server")); + goto out; + } + + dec_in = (gchar *)purple_base64_decode(enc_in, &len); + g_free(enc_in); + if (!dec_in || len != strlen(dec_in)) { + /* Danger afoot; SCRAM shouldn't contain NUL bytes */ + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Malicious challenge from server")); + goto out; + } + + purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in); + + if (!jabber_scram_feed_parser(data, dec_in, &dec_out)) { + reply = xmlnode_new("abort"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + data->step = -1; + *error = g_strdup(_("Invalid challenge from server")); + goto out; + } + + data->step += 1; + + reply = xmlnode_new("response"); + xmlnode_set_namespace(reply, NS_XMPP_SASL); + + purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)"); + if (dec_out) { + enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); + xmlnode_insert_data(reply, enc_out, -1); + } + + state = JABBER_SASL_STATE_CONTINUE; + +out: + g_free(enc_out); + g_free(dec_out); + + *out = reply; + return state; +} + +static JabberSaslState +scram_handle_success(JabberStream *js, xmlnode *packet, char **error) +{ + JabberScramData *data = js->auth_mech_data; + char *enc_in, *dec_in; + char *dec_out = NULL; + gsize len; + + enc_in = xmlnode_get_data(packet); + g_return_val_if_fail(enc_in != NULL && *enc_in != '\0', FALSE); + + if (data->step == 3) + return JABBER_SASL_STATE_OK; + + if (data->step != 2) { + *error = g_strdup(_("Unexpected response from server")); + return JABBER_SASL_STATE_FAIL; + } + + dec_in = (gchar *)purple_base64_decode(enc_in, &len); + g_free(enc_in); + if (!dec_in || len != strlen(dec_in)) { + /* Danger afoot; SCRAM shouldn't contain NUL bytes */ + g_free(dec_in); + *error = g_strdup(_("Invalid challenge from server")); + return JABBER_SASL_STATE_FAIL; + } + + purple_debug_misc("jabber", "decoded success: %s\n", dec_in); + + if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) { + g_free(dec_out); + *error = g_strdup(_("Invalid challenge from server")); + return JABBER_SASL_STATE_FAIL; + } + + /* Hooray */ + return JABBER_SASL_STATE_OK; +} + +void jabber_scram_data_destroy(JabberScramData *data) +{ + g_free(data->cnonce); + if (data->auth_message) + g_string_free(data->auth_message, TRUE); + if (data->client_proof) + g_string_free(data->client_proof, TRUE); + if (data->server_signature) + g_string_free(data->server_signature, TRUE); + if (data->password) { + memset(data->password, 0, strlen(data->password)); + g_free(data->password); + } + + g_free(data); +} + +static void scram_dispose(JabberStream *js) +{ + if (js->auth_mech_data) { + jabber_scram_data_destroy(js->auth_mech_data); + js->auth_mech_data = NULL; + } +} + +static JabberSaslMech scram_sha1_mech = { + 50, /* priority */ + "SCRAM-SHA-1", /* name */ + scram_start, + scram_handle_challenge, + scram_handle_success, + NULL, /* handle_failure */ + scram_dispose +}; + +#ifdef CHANNEL_BINDING +/* With channel binding */ +static JabberSaslMech scram_sha1_plus_mech = { + scram_sha1_mech.priority + 1, /* priority */ + "SCRAM-SHA-1-PLUS", /* name */ + scram_start, + scram_handle_challenge, + scram_handle_success, + NULL, /* handle_failure */ + scram_dispose +}; +#endif + +JabberSaslMech **jabber_auth_get_scram_mechs(gint *count) +{ + static JabberSaslMech *mechs[] = { + &scram_sha1_mech, +#ifdef CHANNEL_BINDING + &scram_sha1_plus_mech, +#endif + }; + + *count = G_N_ELEMENTS(mechs); + return mechs; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/auth_scram.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/jabber/auth_scram.h Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,95 @@ +/** + * @file auth_scram.h Implementation of SASL-SCRAM authentication + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ +#ifndef PURPLE_JABBER_AUTH_SCRAM_H_ +#define PURPLE_JABBER_AUTH_SCRAM_H_ + +/* + * Every function in this file is ONLY exposed for tests. + * DO NOT USE ANYTHING HERE OR YOU WILL BE SENT TO THE PIT OF DESPAIR. + */ + +/* Per-connection state stored between messages. + * This is stored in js->auth_data_mech. + */ +typedef struct { + const char *mech_substr; + const char *name; + guint size; +} JabberScramHash; + +typedef struct { + const JabberScramHash *hash; + char *cnonce; + GString *auth_message; + + GString *client_proof; + GString *server_signature; + + gchar *password; + gboolean channel_binding; + int step; +} JabberScramData; + +#include "auth.h" + +/** + * Implements the Hi() function as described in the SASL-SCRAM I-D. + * + * @param hash The struct corresponding to the hash function to be used. + * @param str The string to perform the PBKDF2 operation on. + * @param salt The salt. + * @param iterations The number of iterations to perform. + * + * @returns A newly allocated string containing the result. The string is + * NOT null-terminated and its length is the length of the binary + * output of the hash function in-use. + */ +guchar *jabber_scram_hi(const JabberScramHash *hash, const GString *str, + GString *salt, guint iterations); + +/** + * Calculates the proofs as described in Section 3 of the SASL-SCRAM I-D. + * + * @param data A JabberScramData structure. hash and auth_message must be + * set. client_proof and server_signature will be set as a result + * of this function. + * @param salt The salt (as specified by the server) + * @param iterations The number of iterations to perform. + * + * @returns TRUE if the proofs were successfully calculated. FALSE otherwise. + */ +gboolean jabber_scram_calc_proofs(JabberScramData *data, GString *salt, + guint iterations); + +/** + * Feed the algorithm with the data from the server. + */ +gboolean jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out); + +/** + * Clean up and destroy the data struct + */ +void jabber_scram_data_destroy(JabberScramData *data); + +#endif /* PURPLE_JABBER_AUTH_SCRAM_H_ */ diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/buddy.c Thu Dec 17 16:50:12 2009 +0900 @@ -815,20 +815,32 @@ if (!jbi->jb->resources) { /* the buddy is offline */ - gchar *status = - g_strdup_printf("%s%s%s", _("Offline"), - jbi->last_message ? ": " : "", - jbi->last_message ? jbi->last_message : ""); + gboolean is_domain = jabber_jid_is_domain(jbi->jid); + if (jbi->last_seconds > 0) { char *last = purple_str_seconds_to_string(jbi->last_seconds); - gchar *message = g_strdup_printf(_("%s ago"), last); - purple_notify_user_info_prepend_pair(user_info, - _("Logged Off"), message); + gchar *message = NULL; + const gchar *title = NULL; + if (is_domain) { + title = _("Uptime"); + message = g_strdup_printf(_("%s"), last); + } else { + title = _("Logged Off"); + message = g_strdup_printf(_("%s ago"), last); + } + purple_notify_user_info_prepend_pair(user_info, title, message); g_free(last); g_free(message); } - purple_notify_user_info_prepend_pair(user_info, _("Status"), status); - g_free(status); + + if (!is_domain) { + gchar *status = + g_strdup_printf("%s%s%s", _("Offline"), + jbi->last_message ? ": " : "", + jbi->last_message ? jbi->last_message : ""); + purple_notify_user_info_prepend_pair(user_info, _("Status"), status); + g_free(status); + } } g_free(resource_name); @@ -1860,8 +1872,10 @@ * However, since the gateway might appear offline to us, we cannot get that information. Therefore, I just assume * that gateways on the roster can be identified by having no '@' in their jid. This is a faily safe assumption, since * people don't tend to have a server or other service there. + * + * TODO: Use disco#info... */ - if (g_utf8_strchr(name, -1, '@') == NULL) { + if (strchr(name, '@') == NULL) { act = purple_menu_action_new(_("Log In"), PURPLE_CALLBACK(jabber_buddy_login), NULL, NULL); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/chat.c Thu Dec 17 16:50:12 2009 +0900 @@ -927,7 +927,7 @@ jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; - else if (g_utf8_strchr(who, -1, '@') != NULL) + else if (strchr(who, '@') != NULL) jid = who; else return FALSE; @@ -964,7 +964,7 @@ jcm = g_hash_table_lookup(chat->members, who); if (jcm && jcm->jid) jid = jcm->jid; - else if (g_utf8_strchr(who, -1, '@') != NULL) + else if (strchr(who, '@') != NULL) jid = who; else return FALSE; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/disco.c Thu Dec 17 16:50:12 2009 +0900 @@ -370,10 +370,8 @@ if (js->pep) jabber_avatar_fetch_mine(js); - if (!(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) { - /* If the server supports JABBER_CAP_GOOGLE_ROSTER; we will have already requested it */ - jabber_roster_request(js); - } + /* Yes, please! */ + jabber_roster_request(js); if (js->server_caps & JABBER_CAP_ADHOC) { /* The server supports ad-hoc commands, so let's request the list */ @@ -555,9 +553,8 @@ if (!strcmp(NS_GOOGLE_MAIL_NOTIFY, var)) { js->server_caps |= JABBER_CAP_GMAIL_NOTIFY; jabber_gmail_init(js); - } else if (!strcmp("google:roster", var)) { + } else if (!strcmp(NS_GOOGLE_ROSTER, var)) { js->server_caps |= JABBER_CAP_GOOGLE_ROSTER; - jabber_google_roster_init(js); } else if (!strcmp("http://jabber.org/protocol/commands", var)) { js->server_caps |= JABBER_CAP_ADHOC; } else if (!strcmp(NS_SIMPLE_BLOCKING, var)) { diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/google.c --- a/libpurple/protocols/jabber/google.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/google.c Thu Dec 17 16:50:12 2009 +0900 @@ -31,6 +31,7 @@ #include "google.h" #include "jabber.h" #include "presence.h" +#include "roster.h" #include "iq.h" #include "chat.h" @@ -950,20 +951,6 @@ jabber_iq_send(iq); } -void jabber_google_roster_init(JabberStream *js) -{ - JabberIq *iq; - xmlnode *query; - - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); - query = xmlnode_get_child(iq->node, "query"); - - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); - xmlnode_set_attrib(query, "gr:ext", "2"); - - jabber_iq_send(iq); -} - void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item) { PurpleAccount *account = purple_connection_get_account(js->gc); @@ -973,7 +960,7 @@ while (list) { if (!strcmp(jid_norm, (char*)list->data)) { - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); xmlnode_set_attrib(item, "gr:t", "B"); return; @@ -990,7 +977,7 @@ char *jid_norm; - const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); + const char *grt = xmlnode_get_attrib_with_namespace(item, "t", NS_GOOGLE_ROSTER); const char *subscription = xmlnode_get_attrib(item, "subscription"); const char *ask = xmlnode_get_attrib(item, "ask"); @@ -1033,9 +1020,9 @@ return TRUE; } -void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who) +void jabber_google_roster_add_deny(JabberStream *js, const char *who) { - JabberStream *js; + PurpleAccount *account; GSList *buddies; JabberIq *iq; xmlnode *query; @@ -1045,14 +1032,10 @@ JabberBuddy *jb; const char *balias; - js = (JabberStream*)(gc->proto_data); - - if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) - return; - jb = jabber_buddy_find(js, who, TRUE); - buddies = purple_find_buddies(js->gc->account, who); + account = purple_connection_get_account(js->gc); + buddies = purple_find_buddies(account, who); if(!buddies) return; @@ -1079,7 +1062,7 @@ xmlnode_set_attrib(item, "jid", who); xmlnode_set_attrib(item, "name", balias ? balias : ""); xmlnode_set_attrib(item, "gr:t", "B"); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); jabber_iq_send(iq); @@ -1099,12 +1082,11 @@ } } - purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL); + purple_prpl_got_user_status(account, who, "offline", NULL); } -void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who) +void jabber_google_roster_rem_deny(JabberStream *js, const char *who) { - JabberStream *js; GSList *buddies; JabberIq *iq; xmlnode *query; @@ -1113,14 +1095,6 @@ PurpleBuddy *b; const char *balias; - g_return_if_fail(gc != NULL); - g_return_if_fail(who != NULL); - - js = (JabberStream*)(gc->proto_data); - - if (!js || !(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) - return; - buddies = purple_find_buddies(purple_connection_get_account(js->gc), who); if(!buddies) return; @@ -1147,7 +1121,7 @@ balias = purple_buddy_get_local_buddy_alias(b); xmlnode_set_attrib(item, "jid", who); xmlnode_set_attrib(item, "name", balias ? balias : ""); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); jabber_iq_send(iq); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/google.h --- a/libpurple/protocols/jabber/google.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/google.h Thu Dec 17 16:50:12 2009 +0900 @@ -32,7 +32,6 @@ void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *new_mail); -void jabber_google_roster_init(JabberStream *js); void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); /* Returns FALSE if this should short-circuit processing of this roster item, or TRUE @@ -43,8 +42,8 @@ void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); char *jabber_google_presence_outgoing(PurpleStatus *tune); -void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who); -void jabber_google_roster_rem_deny(PurpleConnection *gc, const char *who); +void jabber_google_roster_add_deny(JabberStream *js, const char *who); +void jabber_google_roster_rem_deny(JabberStream *js, const char *who); char *jabber_google_format_to_html(const char *text); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jabber.c Thu Dec 17 16:50:12 2009 +0900 @@ -229,13 +229,15 @@ jabber_iq_set_callback(iq, jabber_bind_result_cb, NULL); jabber_iq_send(iq); + } else if (xmlnode_get_child_with_namespace(packet, "ver", NS_ROSTER_VERSIONING)) { + js->server_caps |= JABBER_CAP_ROSTER_VERSIONING; } else /* if(xmlnode_get_child_with_namespace(packet, "auth")) */ { /* If we get an empty stream:features packet, or we explicitly get * an auth feature with namespace http://jabber.org/features/iq-auth * we should revert back to iq:auth authentication, even though we're * connecting to an XMPP server. */ - js->auth_type = JABBER_AUTH_IQ_AUTH; jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); + jabber_auth_start_old(js); } } @@ -639,10 +641,11 @@ js->srv_query_data = NULL; if (responses == NULL) { + purple_debug_warning("jabber", "Unable to find alternative XMPP connection " + "methods after failing to connect directly."); purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Unable to find alternative XMPP connection " - "methods after failing to connect directly.")); + _("Unable to connect")); return; } @@ -1118,7 +1121,8 @@ if(cbdata->js->registration) { username = g_strdup_printf("%s@%s%s%s", cbdata->js->user->node, cbdata->js->user->domain, - cbdata->js->user->resource ? "/" : "", cbdata->js->user->resource); + cbdata->js->user->resource ? "/" : "", + cbdata->js->user->resource ? cbdata->js->user->resource : ""); purple_account_set_username(cbdata->js->gc->account, username); g_free(username); } @@ -1511,6 +1515,8 @@ purple_circ_buffer_destroy(js->write_buffer); if(js->writeh) purple_input_remove(js->writeh); + if (js->auth_mech && js->auth_mech->dispose) + js->auth_mech->dispose(js); #ifdef HAVE_CYRUS_SASL if(js->sasl) sasl_dispose(&js->sasl); @@ -1587,11 +1593,6 @@ case JABBER_STREAM_AUTHENTICATING: purple_connection_update_progress(js->gc, _("Authenticating"), js->gsc ? 7 : 3, JABBER_CONNECT_STEPS); - if(js->protocol_version == JABBER_PROTO_0_9 && js->registration) { - jabber_register_start(js); - } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) { - jabber_auth_start_old(js); - } break; case JABBER_STREAM_POST_AUTH: purple_connection_update_progress(js->gc, _("Re-initializing Stream"), @@ -1746,13 +1747,15 @@ JabberIq *iq; xmlnode *block, *item; - js = gc->proto_data; + g_return_if_fail(who != NULL && *who != '\0'); + + js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { - jabber_google_roster_add_deny(gc, who); + jabber_google_roster_add_deny(js, who); return; } @@ -1780,13 +1783,15 @@ JabberIq *iq; xmlnode *unblock, *item; - js = gc->proto_data; + g_return_if_fail(who != NULL && *who != '\0'); + + js = purple_connection_get_protocol_data(gc); if (js == NULL) return; if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { - jabber_google_roster_rem_deny(gc, who); + jabber_google_roster_rem_deny(js, who); return; } @@ -3521,6 +3526,8 @@ jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0); #endif + jabber_auth_init(); + /* IPC functions */ purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature), purple_marshal_BOOLEAN__POINTER_POINTER_POINTER, @@ -3555,6 +3562,7 @@ { purple_plugin_ipc_unregister_all(plugin); + jabber_auth_uninit(); jabber_features_destroy(); jabber_identities_destroy(); } diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jabber.h Thu Dec 17 16:50:12 2009 +0900 @@ -47,6 +47,7 @@ JABBER_CAP_BLOCKING = 1 << 13, JABBER_CAP_ITEMS = 1 << 14, + JABBER_CAP_ROSTER_VERSIONING = 1 << 15, JABBER_CAP_RETRIEVED = 1 << 31 } JabberCapabilities; @@ -66,6 +67,7 @@ #include "namespaces.h" +#include "auth.h" #include "iq.h" #include "jutil.h" #include "xmlnode.h" @@ -106,13 +108,9 @@ JABBER_PROTO_0_9, JABBER_PROTO_1_0 } protocol_version; - enum { - JABBER_AUTH_UNKNOWN, - JABBER_AUTH_DIGEST_MD5, - JABBER_AUTH_PLAIN, - JABBER_AUTH_IQ_AUTH, - JABBER_AUTH_CYRUS - } auth_type; + + JabberSaslMech *auth_mech; + gpointer auth_mech_data; char *stream_id; JabberStreamState state; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jingle/jingle.c --- a/libpurple/protocols/jabber/jingle/jingle.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jingle/jingle.c Thu Dec 17 16:50:12 2009 +0900 @@ -29,12 +29,13 @@ #include "content.h" #include "debug.h" #include "jingle.h" -#include #include "session.h" #include "iceudp.h" #include "rawudp.h" #include "rtp.h" +#include + GType jingle_get_type(const gchar *type) { diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jingle/session.c --- a/libpurple/protocols/jabber/jingle/session.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jingle/session.c Thu Dec 17 16:50:12 2009 +0900 @@ -377,7 +377,7 @@ { JingleSession *session = (JingleSession *)value; const gchar *jid = user_data; - gboolean use_bare = g_utf8_strchr(jid, -1, '/') == NULL; + gboolean use_bare = strchr(jid, '/') == NULL; gchar *remote_jid = jingle_session_get_remote_jid(session); gchar *cmp_jid = use_bare ? jabber_get_bare_jid(remote_jid) : g_strdup(remote_jid); @@ -438,7 +438,7 @@ data.jid = jid; data.ret = NULL; - data.use_bare = g_utf8_strchr(jid, -1, '/') == NULL; + data.use_bare = strchr(jid, '/') == NULL; g_hash_table_foreach(js->sessions, find_by_jid_ghr, &data); return data.ret; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jutil.c --- a/libpurple/protocols/jabber/jutil.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jutil.c Thu Dec 17 16:50:12 2009 +0900 @@ -276,6 +276,42 @@ #endif /* USE_IDN */ } +char *jabber_saslprep(const char *in) +{ +#ifdef USE_IDN + char *out; + + g_return_val_if_fail(in != NULL, NULL); + g_return_val_if_fail(strlen(in) <= sizeof(idn_buffer) - 1, NULL); + + strncpy(idn_buffer, in, sizeof(idn_buffer) - 1); + idn_buffer[sizeof(idn_buffer) - 1] = '\0'; + + if (STRINGPREP_OK != stringprep(idn_buffer, sizeof(idn_buffer), 0, + stringprep_saslprep)) { + memset(idn_buffer, 0, sizeof(idn_buffer)); + return NULL; + } + + out = g_strdup(idn_buffer); + memset(idn_buffer, 0, sizeof(idn_buffer)); + return out; +#else /* USE_IDN */ + /* TODO: Something better than disallowing all non-ASCII characters */ + /* TODO: Is this even correct? */ + const guchar *c; + + c = (const guchar *)in; + while (*c) { + if (*c > 0x7f || + (*c < 0x20 && *c != '\t' && *c != '\n' && *c != '\r')) + return NULL; + } + + return g_strdup(in); +#endif /* USE_IDN */ +} + static JabberID* jabber_id_new_internal(const char *str, gboolean allow_terminating_slash) { @@ -473,6 +509,19 @@ } } +char *jabber_get_domain(const char *in) +{ + JabberID *jid = jabber_id_new(in); + char *out; + + if (!jid) + return NULL; + + out = g_strdup(jid->domain); + jabber_id_free(jid); + + return out; +} char *jabber_get_resource(const char *in) { @@ -513,6 +562,20 @@ NULL); } +gboolean +jabber_jid_is_domain(const char *jid) +{ + const char *c; + + for (c = jid; *c; ++c) { + if (*c == '@' || *c == '/') + return FALSE; + } + + return TRUE; +} + + JabberID * jabber_id_new(const char *str) { diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/jutil.h --- a/libpurple/protocols/jabber/jutil.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/jutil.h Thu Dec 17 16:50:12 2009 +0900 @@ -35,10 +35,13 @@ JabberID* jabber_id_new(const char *str); void jabber_id_free(JabberID *jid); +char *jabber_get_domain(const char *jid); char *jabber_get_resource(const char *jid); char *jabber_get_bare_jid(const char *jid); char *jabber_id_get_bare_jid(const JabberID *jid); +gboolean jabber_jid_is_domain(const char *jid); + const char *jabber_normalize(const PurpleAccount *account, const char *in); /* Returns true if JID is the bare JID of our server. */ @@ -51,6 +54,15 @@ gboolean jabber_domain_validate(const char *); gboolean jabber_resourceprep_validate(const char *); +/** + * Apply the SASLprep profile of stringprep to the string passed in. + * + * @returns A newly allocated string containing the normalized version + * of the input, or NULL if an error occurred (the string could + * not be normalized) + */ +char *jabber_saslprep(const char *); + PurpleConversation *jabber_find_unnormalized_conv(const char *name, PurpleAccount *account); char *jabber_calculate_data_sha1sum(gconstpointer data, size_t len); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/libxmpp.c Thu Dec 17 16:50:12 2009 +0900 @@ -295,7 +295,7 @@ static gboolean xmpp_uri_handler(const char *proto, const char *user, GHashTable *params) { - char *acct_id = g_hash_table_lookup(params, "account"); + char *acct_id = params ? g_hash_table_lookup(params, "account") : NULL; PurpleAccount *acct; if (g_ascii_strcasecmp(proto, "xmpp")) @@ -307,7 +307,8 @@ return FALSE; /* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */ - if (g_hash_table_lookup_extended(params, "message", NULL, NULL)) { + /* params is NULL if the URI has no '?' (or anything after it) */ + if (!params || g_hash_table_lookup_extended(params, "message", NULL, NULL)) { char *body = g_hash_table_lookup(params, "body"); if (user && *user) { PurpleConversation *conv = diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/namespaces.h --- a/libpurple/protocols/jabber/namespaces.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/namespaces.h Thu Dec 17 16:50:12 2009 +0900 @@ -85,6 +85,9 @@ /* XEP-0231 BoB (Bits of Binary) */ #define NS_BOB "urn:xmpp:bob" +/* XEP-0237 Roster Versioning */ +#define NS_ROSTER_VERSIONING "urn:xmpp:features:rosterver" + /* Google extensions */ #define NS_GOOGLE_CAMERA "http://www.google.com/xmpp/protocol/camera/v1" #define NS_GOOGLE_VIDEO "http://www.google.com/xmpp/protocol/video/v1" @@ -92,6 +95,7 @@ #define NS_GOOGLE_JINGLE_INFO "google:jingleinfo" #define NS_GOOGLE_MAIL_NOTIFY "google:mail:notify" +#define NS_GOOGLE_ROSTER "google:roster" #define NS_GOOGLE_PROTOCOL_SESSION "http://www.google.com/xmpp/protocol/session" #define NS_GOOGLE_SESSION "http://www.google.com/session" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/parser.c --- a/libpurple/protocols/jabber/parser.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/parser.c Thu Dec 17 16:50:12 2009 +0900 @@ -47,9 +47,7 @@ js->protocol_version = JABBER_PROTO_0_9; for(i=0; i < nb_attributes * 5; i += 5) { int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len); if(!xmlStrcmp(attributes[i], (xmlChar*) "version") && !strcmp(attrib, "1.0")) { @@ -88,10 +86,7 @@ const char *attrib_ns = (const char *)attributes[i+2]; char *txt; int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((gchar *)attributes[i+3], attrib_len); txt = attrib; attrib = purple_unescape_html(txt); @@ -152,8 +147,7 @@ */ return; - if (error->level == XML_ERR_FATAL && error->message != NULL - && g_str_equal(error->message, "Extra content at the end of the document\n")) + if (error->level == XML_ERR_FATAL && error->code == XML_ERR_DOCUMENT_END) /* * This is probably more annoying than the vcard-temp error; it occurs * because we disconnect in most cases without waiting for the receiving @@ -269,8 +263,8 @@ * the opening and there was no version, we need to * immediately start legacy IQ auth. */ - js->auth_type = JABBER_AUTH_IQ_AUTH; jabber_stream_set_state(js, JABBER_STREAM_AUTHENTICATING); + jabber_auth_start_old(js); } } diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/presence.c Thu Dec 17 16:50:12 2009 +0900 @@ -150,7 +150,7 @@ /* we don't want to send presence before we've gotten our roster */ if (js->state != JABBER_STREAM_CONNECTED) { - purple_debug_info("jabber", "attempt to send presence before roster retrieved\n"); + purple_debug_misc("jabber", "attempt to send presence before roster retrieved\n"); return; } @@ -433,7 +433,7 @@ JabberPresenceCapabilities *userdata) { JabberBuddyResource *jbr; - char *resource = g_utf8_strchr(userdata->from, -1, '/'); + char *resource = strchr(userdata->from, '/'); if (resource) resource += 1; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/roster.c --- a/libpurple/protocols/jabber/roster.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/roster.c Thu Dec 17 16:50:12 2009 +0900 @@ -47,12 +47,50 @@ return g_string_free(out, FALSE); } +static void roster_request_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + xmlnode *query; + + if (type == JABBER_IQ_ERROR) { + /* + * This shouldn't happen in any real circumstances and + * likely constitutes a server-side misconfiguration (i.e. + * explicitly not loading mod_roster...) + */ + purple_debug_error("jabber", "Error retrieving roster!?\n"); + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + return; + } + + query = xmlnode_get_child(packet, "query"); + if (query == NULL) { + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + return; + } + + jabber_roster_parse(js, from, type, id, query); + jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); +} + void jabber_roster_request(JabberStream *js) { + PurpleAccount *account; JabberIq *iq; + xmlnode *query; + + account = purple_connection_get_account(js->gc); iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); + query = xmlnode_get_child(iq->node, "query"); + if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); + xmlnode_set_attrib(query, "gr:ext", "2"); + } + + jabber_iq_set_callback(iq, roster_request_cb, NULL); jabber_iq_send(iq); } @@ -155,6 +193,7 @@ JabberIqType type, const char *id, xmlnode *query) { xmlnode *item, *group; + const char *ver; if (!jabber_is_own_account(js, from)) { purple_debug_warning("jabber", "Received bogon roster push from %s\n", @@ -181,18 +220,18 @@ continue; if(subscription) { - if (jb == js->user_jb) + if (g_str_equal(subscription, "remove")) + jb->subscription = JABBER_SUB_REMOVE; + else if (jb == js->user_jb) jb->subscription = JABBER_SUB_BOTH; - else if(!strcmp(subscription, "none")) + else if (g_str_equal(subscription, "none")) jb->subscription = JABBER_SUB_NONE; - else if(!strcmp(subscription, "to")) + else if (g_str_equal(subscription, "to")) jb->subscription = JABBER_SUB_TO; - else if(!strcmp(subscription, "from")) + else if (g_str_equal(subscription, "from")) jb->subscription = JABBER_SUB_FROM; - else if(!strcmp(subscription, "both")) + else if (g_str_equal(subscription, "both")) jb->subscription = JABBER_SUB_BOTH; - else if(!strcmp(subscription, "remove")) - jb->subscription = JABBER_SUB_REMOVE; } if(purple_strequal(ask, "subscribe")) @@ -227,13 +266,13 @@ } } - js->currently_parsing_roster_push = FALSE; + ver = xmlnode_get_attrib(query, "ver"); + if (ver) { + PurpleAccount *account = purple_connection_get_account(js->gc); + purple_account_set_string(account, "roster_ver", ver); + } - /* if we're just now parsing the roster for the first time, - * then now would be the time to declare ourselves connected. - */ - if (js->state != JABBER_STREAM_CONNECTED) - jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); + js->currently_parsing_roster_push = FALSE; } /* jabber_roster_update frees the GSList* passed in */ @@ -297,7 +336,7 @@ if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { jabber_google_roster_outgoing(js, query, item); - xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); } jabber_iq_send(iq); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/jabber/si.c --- a/libpurple/protocols/jabber/si.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/jabber/si.c Thu Dec 17 16:50:12 2009 +0900 @@ -1459,7 +1459,7 @@ g_free(xfer->who); xfer->who = who; - if (jbr) { + if (jbr && jabber_resource_know_capabilities(jbr)) { char *msg; if (jabber_resource_has_capability(jbr, NS_IBB)) diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slp.c Thu Dec 17 16:50:12 2009 +0900 @@ -126,22 +126,60 @@ g_free(content); msn_slplink_send_queued_slpmsgs(slpcall->slplink); - msn_slpcall_destroy(slpcall); + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + slpcall->wasted = TRUE; + else + msn_slpcall_destroy(slpcall); } } } -void -msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset) +gssize +msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer) { - PurpleXfer *xfer; + MsnSlpCall *slpcall; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1); + + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); + + g_return_val_if_fail(slpcall->xfer_msg != NULL, -1); + + slpcall->u.outgoing.len = len; + slpcall->u.outgoing.data = data; + msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg); + return MIN(1202, len); +} - xfer = slpcall->xfer; +gssize +msn_xfer_read(guchar **data, PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + gsize len; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1); - xfer->bytes_sent = (offset + len); - xfer->bytes_remaining = total_length - (offset + len); + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); - purple_xfer_update_progress(xfer); + /* Just pass up the whole GByteArray. We'll make another. */ + *data = slpcall->u.incoming_data->data; + len = slpcall->u.incoming_data->len; + + g_byte_array_free(slpcall->u.incoming_data, FALSE); + slpcall->u.incoming_data = g_byte_array_new(); + + return len; } void @@ -332,9 +370,7 @@ account = slpcall->slplink->session->account; - slpcall->cb = msn_xfer_completed_cb; slpcall->end_cb = msn_xfer_end_cb; - slpcall->progress_cb = msn_xfer_progress_cb; slpcall->branch = g_strdup(branch); slpcall->pending = TRUE; @@ -357,6 +393,10 @@ purple_xfer_set_init_fnc(xfer, msn_xfer_init); purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); + + slpcall->u.incoming_data = g_byte_array_new(); slpcall->xfer = xfer; purple_xfer_ref(slpcall->xfer); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slp.h --- a/libpurple/protocols/msn/slp.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slp.h Thu Dec 17 16:50:12 2009 +0900 @@ -29,9 +29,6 @@ #include "internal.h" #include "ft.h" -void msn_xfer_progress_cb(MsnSlpCall *slpcall, gsize total_length, gsize - len, gsize offset); - MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink, const char *body); @@ -41,6 +38,9 @@ const guchar *body, gsize size); void msn_xfer_cancel(PurpleXfer *xfer); +gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer); +gssize msn_xfer_read(guchar **data, PurpleXfer *xfer); + void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session); void msn_queue_buddy_icon_request(MsnUser *user); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slpcall.c --- a/libpurple/protocols/msn/slpcall.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slpcall.c Thu Dec 17 16:50:12 2009 +0900 @@ -105,10 +105,13 @@ slpcall->end_cb(slpcall, slpcall->slplink->session); if (slpcall->xfer != NULL) { + if (purple_xfer_get_type(slpcall->xfer) == PURPLE_XFER_RECEIVE) + g_byte_array_free(slpcall->u.incoming_data, TRUE); slpcall->xfer->data = NULL; purple_xfer_unref(slpcall->xfer); } + msn_slplink_remove_slpcall(slpcall->slplink, slpcall); g_free(slpcall->id); @@ -272,7 +275,8 @@ slpcall->timer = 0; } - slpcall->cb(slpcall, body, body_len); + if (slpcall->cb) + slpcall->cb(slpcall, body, body_len); slpcall->wasted = TRUE; } diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slpcall.h --- a/libpurple/protocols/msn/slpcall.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slpcall.h Thu Dec 17 16:50:12 2009 +0900 @@ -72,6 +72,14 @@ char *data_info; PurpleXfer *xfer; + union { + GByteArray *incoming_data; + struct { + gsize len; + const guchar *data; + } outgoing; + } u; + MsnSlpMessage *xfer_msg; /* A dirty hack */ MsnSlpCb cb; void (*end_cb)(MsnSlpCall *slpcall, MsnSession *session); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slplink.c --- a/libpurple/protocols/msn/slplink.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slplink.c Thu Dec 17 16:50:12 2009 +0900 @@ -232,7 +232,7 @@ } } -static void +void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { MsnMessage *msg; @@ -247,11 +247,11 @@ if (slpmsg->offset < real_size) { - if (slpmsg->fp) + if (slpmsg->slpcall && slpmsg->slpcall->xfer && purple_xfer_get_type(slpmsg->slpcall->xfer) == PURPLE_XFER_SEND && + purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) { - char data[1202]; - len = fread(data, 1, sizeof(data), slpmsg->fp); - msn_message_set_bin_data(msg, data, len); + len = MIN(1202, slpmsg->slpcall->u.outgoing.len); + msn_message_set_bin_data(msg, slpmsg->slpcall->u.outgoing.data, len); } else { @@ -309,7 +309,13 @@ if (slpmsg->offset < real_size) { - msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) + { + slpmsg->slpcall->xfer_msg = slpmsg; + purple_xfer_prpl_ready(slpmsg->slpcall->xfer); + } + else + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); } else { @@ -448,20 +454,22 @@ send_file_cb(MsnSlpCall *slpcall) { MsnSlpMessage *slpmsg; - struct stat st; PurpleXfer *xfer; + xfer = (PurpleXfer *)slpcall->xfer; + purple_xfer_ref(xfer); + purple_xfer_start(xfer, -1, NULL, 0); + if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) { + purple_xfer_unref(xfer); + return; + } + purple_xfer_unref(xfer); + slpmsg = msn_slpmsg_new(slpcall->slplink); slpmsg->slpcall = slpcall; slpmsg->flags = 0x1000030; slpmsg->info = "SLP FILE"; - - xfer = (PurpleXfer *)slpcall->xfer; - purple_xfer_start(slpcall->xfer, -1, NULL, 0); - slpmsg->fp = xfer->dest_fp; - if (g_stat(purple_xfer_get_local_filename(xfer), &st) == 0) - slpmsg->size = st.st_size; - xfer->dest_fp = NULL; /* Disable double fclose() */ + slpmsg->size = purple_xfer_get_size(xfer); msn_slplink_send_slpmsg(slpcall->slplink, slpmsg); } @@ -489,6 +497,7 @@ const char *data; guint64 offset; gsize len; + PurpleXfer *xfer = NULL; if (purple_debug_is_verbose()) msn_slpmsg_show(msg); @@ -525,12 +534,12 @@ if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) { - PurpleXfer *xfer; - xfer = slpmsg->slpcall->xfer; - if (xfer != NULL) { + slpmsg->ft = TRUE; + slpmsg->slpcall->xfer_msg = slpmsg; + purple_xfer_ref(xfer); purple_xfer_start(xfer, -1, NULL, 0); @@ -540,14 +549,12 @@ g_return_if_reached(); } else { purple_xfer_unref(xfer); - slpmsg->fp = xfer->dest_fp; - xfer->dest_fp = NULL; /* Disable double fclose() */ } } } } } - if (!slpmsg->fp && slpmsg->size) + if (!slpmsg->ft && slpmsg->size) { slpmsg->buffer = g_try_malloc(slpmsg->size); if (slpmsg->buffer == NULL) @@ -569,10 +576,12 @@ } } - if (slpmsg->fp) + if (slpmsg->ft) { - /* fseek(slpmsg->fp, offset, SEEK_SET); */ - len = fwrite(data, 1, len, slpmsg->fp); + xfer = slpmsg->slpcall->xfer; + slpmsg->slpcall->u.incoming_data = + g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)data, len); + purple_xfer_prpl_ready(xfer); } else if (slpmsg->size && slpmsg->buffer) { @@ -613,29 +622,37 @@ slpcall = msn_slp_process_msg(slplink, slpmsg); - if (slpmsg->flags == 0x100) - { - MsnDirectConn *directconn; + if (slpcall == NULL) { + msn_slpmsg_destroy(slpmsg); + return; + } - directconn = slplink->directconn; + if (!slpcall->wasted) { + if (slpmsg->flags == 0x100) + { + MsnDirectConn *directconn; + + directconn = slplink->directconn; #if 0 - if (!directconn->acked) - msn_directconn_send_handshake(directconn); + if (!directconn->acked) + msn_directconn_send_handshake(directconn); #endif - } - else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || - slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) - { - /* Release all the messages and send the ACK */ + } + else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || + slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || + slpmsg->flags == 0x1000030) + { + /* Release all the messages and send the ACK */ - msn_slplink_send_ack(slplink, msg); - msn_slplink_send_queued_slpmsgs(slplink); + msn_slplink_send_ack(slplink, msg); + msn_slplink_send_queued_slpmsgs(slplink); + } + } msn_slpmsg_destroy(slpmsg); - if (slpcall != NULL && slpcall->wasted) + if (slpcall->wasted) msn_slpcall_destroy(slpcall); } } @@ -732,7 +749,6 @@ slpcall->session_init_cb = send_file_cb; slpcall->end_cb = msn_xfer_end_cb; - slpcall->progress_cb = msn_xfer_progress_cb; slpcall->cb = msn_xfer_completed_cb; slpcall->xfer = xfer; purple_xfer_ref(slpcall->xfer); @@ -740,6 +756,8 @@ slpcall->pending = TRUE; purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); xfer->data = slpcall; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slplink.h --- a/libpurple/protocols/msn/slplink.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slplink.h Thu Dec 17 16:50:12 2009 +0900 @@ -84,6 +84,9 @@ void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg); void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer); +/* Only exported for msn_xfer_write */ +void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); + void msn_slplink_request_object(MsnSlpLink *slplink, const char *info, MsnSlpCb cb, diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slpmsg.c --- a/libpurple/protocols/msn/slpmsg.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slpmsg.c Thu Dec 17 16:50:12 2009 +0900 @@ -60,9 +60,6 @@ slplink = slpmsg->slplink; - if (slpmsg->fp != NULL) - fclose(slpmsg->fp); - purple_imgstore_unref(slpmsg->img); /* We don't want to free the data of the PurpleStoredImage, @@ -96,7 +93,7 @@ /* We can only have one data source at a time. */ g_return_if_fail(slpmsg->buffer == NULL); g_return_if_fail(slpmsg->img == NULL); - g_return_if_fail(slpmsg->fp == NULL); + g_return_if_fail(slpmsg->ft == FALSE); if (body != NULL) slpmsg->buffer = g_memdup(body, size); @@ -112,7 +109,7 @@ /* We can only have one data source at a time. */ g_return_if_fail(slpmsg->buffer == NULL); g_return_if_fail(slpmsg->img == NULL); - g_return_if_fail(slpmsg->fp == NULL); + g_return_if_fail(slpmsg->ft == FALSE); slpmsg->img = purple_imgstore_ref(img); slpmsg->buffer = (guchar *)purple_imgstore_get_data(img); diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/slpmsg.h --- a/libpurple/protocols/msn/slpmsg.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/slpmsg.h Thu Dec 17 16:50:12 2009 +0900 @@ -54,7 +54,7 @@ gboolean sip; /**< A flag that states if this is a SIP slp message. */ long flags; - FILE *fp; + gboolean ft; PurpleStoredImage *img; guchar *buffer; long long offset; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/switchboard.c Thu Dec 17 16:50:12 2009 +0900 @@ -222,13 +222,28 @@ { MsnCmdProc *cmdproc; PurpleAccount *account; + char *semicolon; + char *passport; g_return_if_fail(swboard != NULL); cmdproc = swboard->cmdproc; account = cmdproc->session->account; - swboard->users = g_list_prepend(swboard->users, g_strdup(user)); + semicolon = strchr(user, ';'); + /* We don't really care about the machine ID. */ + if (semicolon) + passport = g_strndup(user, semicolon - user); + else + passport = g_strdup(user); + + /* Don't add multiple endpoints to the conversation. */ + if (g_list_find_custom(swboard->users, passport, (GCompareFunc)strcmp)) { + g_free(passport); + return; + } + + swboard->users = g_list_prepend(swboard->users, passport); swboard->current_users++; swboard->empty = FALSE; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/msn/userlist.c --- a/libpurple/protocols/msn/userlist.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/msn/userlist.c Thu Dec 17 16:50:12 2009 +0900 @@ -50,8 +50,11 @@ { MsnSession *session = pa->gc->proto_data; MsnUserList *userlist = session->userlist; + PurpleAccount *account = purple_connection_get_account(pa->gc); msn_userlist_add_buddy_to_list(userlist, pa->who, MSN_LIST_AL); + purple_privacy_deny_remove(account, pa->who, TRUE); + purple_privacy_permit_add(account, pa->who, TRUE); msn_del_contact_from_list(session, NULL, pa->who, MSN_LIST_PL); } diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/actions.c --- a/libpurple/protocols/mxit/actions.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/actions.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/chunk.c --- a/libpurple/protocols/mxit/chunk.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/chunk.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" #include "mxit.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/cipher.c --- a/libpurple/protocols/mxit/cipher.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/cipher.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/filexfer.c --- a/libpurple/protocols/mxit/filexfer.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/filexfer.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" #include "mxit.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/formcmds.c --- a/libpurple/protocols/mxit/formcmds.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/formcmds.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,8 +23,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include + +#include "internal.h" #include #include "purple.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/http.c --- a/libpurple/protocols/mxit/http.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/http.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/login.c --- a/libpurple/protocols/mxit/login.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/login.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/markup.c --- a/libpurple/protocols/mxit/markup.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/markup.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/multimx.c --- a/libpurple/protocols/mxit/multimx.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/multimx.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "prpl.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/mxit.c --- a/libpurple/protocols/mxit/mxit.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/mxit.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "notify.h" #include "plugin.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/profile.c --- a/libpurple/protocols/mxit/profile.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/profile.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,9 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include - +#include "internal.h" #include "purple.h" #include "mxit.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/protocol.c --- a/libpurple/protocols/mxit/protocol.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/protocol.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,11 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/roster.c --- a/libpurple/protocols/mxit/roster.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/roster.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,10 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include -#include -#include - +#include "internal.h" #include "purple.h" #include "protocol.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/mxit/splashscreen.c --- a/libpurple/protocols/mxit/splashscreen.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/mxit/splashscreen.c Thu Dec 17 16:50:12 2009 +0900 @@ -23,6 +23,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "internal.h" #include #include "purple.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/myspace/user.c --- a/libpurple/protocols/myspace/user.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/myspace/user.c Thu Dec 17 16:50:12 2009 +0900 @@ -70,6 +70,8 @@ if (!user) return; + purple_util_fetch_url_cancel(user->url_data); + g_free(user->client_info); g_free(user->gender); g_free(user->location); @@ -212,6 +214,8 @@ const char *name = purple_buddy_get_name(user->buddy); PurpleAccount *account; + user->url_data = NULL; + purple_debug_info("msim_downloaded_buddy_icon", "Downloaded %" G_GSIZE_FORMAT " bytes\n", len); @@ -375,7 +379,7 @@ /* Only download if URL changed */ if (!previous_url || !g_str_equal(previous_url, user->image_url)) { - purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user); + user->url_data = purple_util_fetch_url(user->image_url, TRUE, NULL, TRUE, msim_downloaded_buddy_icon, (gpointer)user); } } else if (g_str_equal(key_str, "LastImageUpdated")) { /* TODO: use somewhere */ diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/myspace/user.h --- a/libpurple/protocols/myspace/user.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/myspace/user.h Thu Dec 17 16:50:12 2009 +0900 @@ -40,6 +40,7 @@ gchar *image_url; guint last_image_updated; gboolean temporary_user; + PurpleUtilFetchUrlData *url_data; } MsimUser; /* Callback function pointer type for when a user's information is received, diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/oscar/clientlogin.c --- a/libpurple/protocols/oscar/clientlogin.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/oscar/clientlogin.c Thu Dec 17 16:50:12 2009 +0900 @@ -36,12 +36,12 @@ * http://dev.aol.com/authentication_for_clients */ +#include "oscar.h" +#include "oscarcommon.h" + #include "cipher.h" #include "core.h" -#include "oscar.h" -#include "oscarcommon.h" - #define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin" #define URL_START_OSCAR_SESSION "http://api.oscar.aol.com/aim/startOSCARSession" @@ -275,13 +275,20 @@ char *query_string, *signature, *url; gboolean use_tls = purple_account_get_bool(purple_connection_get_account(od->gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); - /* Construct the GET parameters */ + /* + * Construct the GET parameters. 0x00000611 is the distid given to + * us by AOL for use as the default libpurple distid. + */ query_string = g_strdup_printf("a=%s" + "&distId=%d" "&f=xml" "&k=%s" "&ts=%" PURPLE_TIME_T_MODIFIER "&useTLS=%d", - purple_url_encode(token), get_client_key(od), hosttime, use_tls); + purple_url_encode(token), + oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" + : "prpl-aim-distid", 0x00000611), + get_client_key(od), hosttime, use_tls); signature = generate_signature("GET", URL_START_OSCAR_SESSION, query_string, session_key); url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s", diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/oscar/family_auth.c --- a/libpurple/protocols/oscar/family_auth.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/oscar/family_auth.c Thu Dec 17 16:50:12 2009 +0900 @@ -26,12 +26,12 @@ * */ +#include "oscar.h" + #include #include "cipher.h" -#include "oscar.h" - /* #define USE_XOR_FOR_ICQ */ #ifdef USE_XOR_FOR_ICQ diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/oscar/oscar.c Thu Dec 17 16:50:12 2009 +0900 @@ -477,10 +477,12 @@ /* Should just be "ASCII" */ charsetstr1 = "ASCII"; charsetstr2 = "UTF-8"; + charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); } else if (charset == AIM_CHARSET_QUIRKUTF8) { /* Mobile AIM client on a Nokia 3100 and an LG VX6000 */ charsetstr1 = "UTF-8"; //iChat use 0x000d when it sends UTF-8. --yaz charsetstr2 = "ISO-8859-1"; + charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); } else { /* Unknown, hope for valid UTF-8... */ charsetstr1 = "UTF-8"; @@ -3607,9 +3609,9 @@ purple_debug_misc("oscar", "created room: %s %hu %hu %hu %u %hu %hu %hhu %hu %s %s\n", - fqcn, exchange, instance, flags, createtime, + fqcn ? fqcn : "(null)", exchange, instance, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, - name, ck); + name ? name : "(null)", ck); aim_chat_join(od, exchange, ck, instance); } break; @@ -4676,7 +4678,7 @@ if ((conn != NULL) && (conn->ready)) { /* If we're directly connected, send a direct IM */ - purple_debug_info("oscar", "Sending direct IM with flags %i", imflags); + purple_debug_info("oscar", "Sending direct IM with flags %i\n", imflags); purple_odc_send_im(conn, tmp1, imflags); } else { struct buddyinfo *bi; diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/oscar/oscarcommon.h --- a/libpurple/protocols/oscar/oscarcommon.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/oscar/oscarcommon.h Thu Dec 17 16:50:12 2009 +0900 @@ -24,8 +24,9 @@ * and libicq.c */ +#include "internal.h" + #include "accountopt.h" -#include "internal.h" #include "prpl.h" #include "version.h" #include "notify.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/protocols/yahoo/ycht.c --- a/libpurple/protocols/yahoo/ycht.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/protocols/yahoo/ycht.c Thu Dec 17 16:50:12 2009 +0900 @@ -25,8 +25,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include - #include "internal.h" #include "prpl.h" #include "notify.h" diff -r 5c77b620375c -r 4491a662d527 libpurple/proxy.c --- a/libpurple/proxy.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/proxy.c Thu Dec 17 16:50:12 2009 +0900 @@ -1099,6 +1099,36 @@ connect_data->host, connect_data->port, connect_data->host, connect_data->port); + if (purple_proxy_info_get_username(connect_data->gpi) != NULL) + { + char *t1, *t2, *ntlm_type1; + char hostname[256]; + + ret = gethostname(hostname, sizeof(hostname)); + hostname[sizeof(hostname) - 1] = '\0'; + if (ret < 0 || hostname[0] == '\0') { + purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?"); + strcpy(hostname, "localhost"); + } + + t1 = g_strdup_printf("%s:%s", + purple_proxy_info_get_username(connect_data->gpi), + purple_proxy_info_get_password(connect_data->gpi) ? + purple_proxy_info_get_password(connect_data->gpi) : ""); + t2 = purple_base64_encode((const guchar *)t1, strlen(t1)); + g_free(t1); + + ntlm_type1 = purple_ntlm_gen_type1(hostname, ""); + + g_string_append_printf(request, + "Proxy-Authorization: Basic %s\r\n" + "Proxy-Authorization: NTLM %s\r\n" + "Proxy-Connection: Keep-Alive\r\n", + t2, ntlm_type1); + g_free(ntlm_type1); + g_free(t2); + } + g_string_append(request, "\r\n"); connect_data->write_buf_len = request->len; diff -r 5c77b620375c -r 4491a662d527 libpurple/tests/Makefile.am --- a/libpurple/tests/Makefile.am Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/tests/Makefile.am Thu Dec 17 16:50:12 2009 +0900 @@ -11,6 +11,7 @@ tests.h \ test_cipher.c \ test_jabber_jutil.c \ + test_jabber_scram.c \ test_qq.c \ test_yahoo_util.c \ test_util.c \ @@ -26,11 +27,11 @@ -DBUILDDIR=\"$(top_builddir)\" check_libpurple_LDADD=\ - @CHECK_LIBS@ \ - $(GLIB_LIBS) \ $(top_builddir)/libpurple/protocols/jabber/libjabber.la \ $(top_builddir)/libpurple/protocols/qq/libqq.la \ $(top_builddir)/libpurple/protocols/yahoo/libymsg.la \ - $(top_builddir)/libpurple/libpurple.la + $(top_builddir)/libpurple/libpurple.la \ + @CHECK_LIBS@ \ + $(GLIB_LIBS) endif diff -r 5c77b620375c -r 4491a662d527 libpurple/tests/check_libpurple.c --- a/libpurple/tests/check_libpurple.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/tests/check_libpurple.c Thu Dec 17 16:50:12 2009 +0900 @@ -44,7 +44,7 @@ purple_eventloop_set_ui_ops(&eventloop_ui_ops); /* build our fake home directory */ - home_dir = g_build_path(BUILDDIR, "libpurple", "tests", "home", NULL); + home_dir = g_build_path(G_DIR_SEPARATOR_S, BUILDDIR, "libpurple", "tests", "home", NULL); purple_util_set_user_dir(home_dir); g_free(home_dir); @@ -67,6 +67,9 @@ int number_failed; SRunner *sr; + if (g_getenv("PURPLE_CHECK_DEBUG")) + purple_debug_set_enabled(TRUE); + /* Make g_return_... functions fatal, ALWAYS. * As this is the test code, this is NOT controlled * by PURPLE_FATAL_ASSERTS. */ @@ -76,6 +79,7 @@ srunner_add_suite(sr, cipher_suite()); srunner_add_suite(sr, jabber_jutil_suite()); + srunner_add_suite(sr, jabber_scram_suite()); srunner_add_suite(sr, qq_suite()); srunner_add_suite(sr, yahoo_util_suite()); srunner_add_suite(sr, util_suite()); diff -r 5c77b620375c -r 4491a662d527 libpurple/tests/test_cipher.c --- a/libpurple/tests/test_cipher.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/tests/test_cipher.c Thu Dec 17 16:50:12 2009 +0900 @@ -142,12 +142,13 @@ PurpleCipherContext *context = NULL; \ gchar cdigest[41]; \ gboolean ret = FALSE; \ + gchar *input = data; \ \ cipher = purple_ciphers_find_cipher("sha1"); \ context = purple_cipher_context_new(cipher, NULL); \ \ - if((data)) { \ - purple_cipher_context_append(context, (guchar *)(data), strlen((data))); \ + if (input) { \ + purple_cipher_context_append(context, (guchar *)input, strlen(input)); \ } else { \ gint j; \ guchar buff[1000]; \ @@ -202,12 +203,13 @@ PurpleCipherContext *context = NULL; \ gchar cdigest[65]; \ gboolean ret = FALSE; \ + gchar *input = data; \ \ cipher = purple_ciphers_find_cipher("sha256"); \ context = purple_cipher_context_new(cipher, NULL); \ \ - if((data)) { \ - purple_cipher_context_append(context, (guchar *)(data), strlen((data))); \ + if (input) { \ + purple_cipher_context_append(context, (guchar *)input, strlen(input)); \ } else { \ gint j; \ guchar buff[1000]; \ diff -r 5c77b620375c -r 4491a662d527 libpurple/tests/test_jabber_scram.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/test_jabber_scram.c Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,117 @@ +#include + +#include "tests.h" +#include "../util.h" +#include "../protocols/jabber/auth_scram.h" +#include "../protocols/jabber/jutil.h" + +static JabberScramHash sha1_mech = { "-SHA-1", "sha1", 20 }; + +#define assert_pbkdf2_equal(password, salt, count, expected) { \ + GString *p = g_string_new(password); \ + GString *s = g_string_new(salt); \ + guchar *result = jabber_scram_hi(&sha1_mech, p, s, count); \ + fail_if(result == NULL, "Hi() returned NULL"); \ + fail_if(0 != memcmp(result, expected, 20), "Hi() returned invalid result"); \ + g_string_free(s, TRUE); \ + g_string_free(p, TRUE); \ +} + +START_TEST(test_pbkdf2) +{ + assert_pbkdf2_equal("password", "salt", 1, "\x0c\x60\xc8\x0f\x96\x1f\x0e\x71\xf3\xa9\xb5\x24\xaf\x60\x12\x06\x2f\xe0\x37\xa6"); + assert_pbkdf2_equal("password", "salt", 2, "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57"); + assert_pbkdf2_equal("password", "salt", 4096, "\x4b\x00\x79\x01\xb7\x65\x48\x9a\xbe\xad\x49\xd9\x26\xf7\x21\xd0\x65\xa4\x29\xc1"); + +#if 0 + /* This causes libcheck to time out :-D */ + assert_pbkdf2_equal("password", "salt", 16777216, "\xee\xfe\x3d\x61\xcd\x4d\xa4\xe4\xe9\x94\x5b\x3d\x6b\xa2\x15\x8c\x26\x34\xe9\x84"); +#endif +} +END_TEST + +START_TEST(test_proofs) +{ + JabberScramData *data = g_new0(JabberScramData, 1); + gboolean ret; + GString *salt; + const char *client_proof; +/* const char *server_signature; */ + + data->hash = &sha1_mech; + data->password = g_strdup("password"); + data->auth_message = g_string_new("n=username@jabber.org,r=8jLxB5515dhFxBil5A0xSXMH," + "r=8jLxB5515dhFxBil5A0xSXMHabc,s=c2FsdA==,i=1," + "c=biws,r=8jLxB5515dhFxBil5A0xSXMHabc"); + client_proof = "\x48\x61\x30\xa5\x61\x0b\xae\xb9\xe4\x11\xa8\xfd\xa5\xcd\x34\x1d\x8a\x3c\x28\x17"; + + salt = g_string_new("salt"); + ret = jabber_scram_calc_proofs(data, salt, 1); + fail_if(ret == FALSE, "Failed to calculate SCRAM proofs!"); + + fail_unless(0 == memcmp(client_proof, data->client_proof->str, 20)); + g_string_free(salt, TRUE); + + jabber_scram_data_destroy(data); +} +END_TEST + +#define assert_successful_exchange(pw, nonce, start_data, challenge1, response1, success) { \ + JabberScramData *data = g_new0(JabberScramData, 1); \ + gboolean ret; \ + gchar *out; \ + \ + data->step = 1; \ + data->hash = &sha1_mech; \ + data->password = jabber_saslprep(pw); \ + fail_if(data->password == NULL); \ + data->cnonce = g_strdup(nonce); \ + data->auth_message = g_string_new(start_data); \ + \ + ret = jabber_scram_feed_parser(data, challenge1, &out); \ + fail_unless(ret == TRUE); \ + fail_unless(g_str_equal(out, response1), "Got unexpected response to challenge. Expected %s, got %s", response1, out); \ + g_free(out); \ + \ + data->step = 2; \ + ret = jabber_scram_feed_parser(data, success, &out); \ + fail_unless(ret == TRUE); \ + fail_unless(out == NULL); \ + \ + jabber_scram_data_destroy(data); \ +} + +START_TEST(test_mech) +{ + assert_successful_exchange("password", "H7yDYKAWBCrM2Fa5SxGa4iez", + "n=paul,r=H7yDYKAWBCrM2Fa5SxGa4iez", + "r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,s=3rXeErP/os7jUNqU,i=4096", + "c=biws,r=H7yDYKAWBCrM2Fa5SxGa4iezFPVDPpDUcGxPkH3RzP,p=pXkak78EuwwOEwk2/h/OzD7NkEI=", + "v=ldX4EBNnOgDnNTOCmbSfBHAUCOs="); + + assert_successful_exchange("pass½word", "GNb2HsNI7VnTv8ABsE5AnY8W", + "n=paul,r=GNb2HsNI7VnTv8ABsE5AnY8W", + "r=GNb2HsNI7VnTv8ABsE5AnY8W/w/I3eRKM0I7jxFWOH,s=ysAriUjPzFqOXnMQ,i=4096", + "c=biws,r=GNb2HsNI7VnTv8ABsE5AnY8W/w/I3eRKM0I7jxFWOH,p=n/CtgdWjOYnLQ4m9Na+wPn9D2uY=", + "v=4TkZwKWy6JHNmrUbU2+IdAaXtos="); +} +END_TEST + +Suite * +jabber_scram_suite(void) +{ + Suite *s = suite_create("Jabber SASL SCRAM functions"); + + TCase *tc = tcase_create("PBKDF2 Functionality"); + tcase_add_test(tc, test_pbkdf2); + suite_add_tcase(s, tc); + + tc = tcase_create("SCRAM Proofs"); + tcase_add_test(tc, test_proofs); + suite_add_tcase(s, tc); + + tc = tcase_create("SCRAM exchange"); + tcase_add_test(tc, test_mech); + suite_add_tcase(s, tc); + return s; +} diff -r 5c77b620375c -r 4491a662d527 libpurple/tests/tests.h --- a/libpurple/tests/tests.h Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/tests/tests.h Thu Dec 17 16:50:12 2009 +0900 @@ -10,6 +10,7 @@ Suite * master_suite(void); Suite * cipher_suite(void); Suite * jabber_jutil_suite(void); +Suite * jabber_scram_suite(void); Suite * qq_suite(void); Suite * yahoo_util_suite(void); Suite * util_suite(void); diff -r 5c77b620375c -r 4491a662d527 libpurple/xmlnode.c --- a/libpurple/xmlnode.c Mon Nov 30 16:07:54 2009 +0900 +++ b/libpurple/xmlnode.c Thu Dec 17 16:50:12 2009 +0900 @@ -588,9 +588,7 @@ const char *prefix = (const char *)attributes[i+1]; char *txt; int attrib_len = attributes[i+4] - attributes[i+3]; - char *attrib = g_malloc(attrib_len + 1); - memcpy(attrib, attributes[i+3], attrib_len); - attrib[attrib_len] = '\0'; + char *attrib = g_strndup((const char *)attributes[i+3], attrib_len); txt = attrib; attrib = purple_unescape_html(txt); g_free(txt); diff -r 5c77b620375c -r 4491a662d527 pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Mon Nov 30 16:07:54 2009 +0900 +++ b/pidgin/gtkdialogs.c Thu Dec 17 16:50:12 2009 +0900 @@ -197,7 +197,6 @@ {N_("Kurdish"), "ku", "Amed Ç. Jiyan", "amedcj@hotmail.com"}, {N_("Kurdish"), "ku", "Rizoyê Xerzî", "rizoxerzi@hotmail.com"}, {N_("Lao"), "lo", "Anousak Souphavah", "anousak@gmail.com"}, - {N_("Lithuanian"), "lt", "Laurynas Biveinis", "laurynas.biveinis@gmail.com"}, {N_("Macedonian"), "mk", "Arangel Angov ", "arangel@linux.net.mk"}, {N_("Macedonian"), "mk", "Ivana Kirkovska", "ivana.kirkovska@gmail.com"}, {N_("Macedonian"), "mk", "Jovan Naumovski", "jovan@lugola.net"}, @@ -276,8 +275,9 @@ {N_("Georgian"), "ka", "Temuri Doghonadze", NULL}, {N_("Korean"), "ko", "Sang-hyun S, A Ho-seok Lee", NULL}, {N_("Korean"), "ko", "Kyeong-uk Son", NULL}, + {N_("Lithuanian"), "lt", "Laurynas Biveinis", "laurynas.biveinis@gmail.com"}, + {N_("Lithuanian"), "lt", "Gediminas Čičinskas", NULL}, {N_("Lithuanian"), "lt", "Andrius Štikonas", NULL}, - {N_("Lithuanian"), "lt", "Gediminas Čičinskas", NULL}, {N_("Macedonian"), "mk", "Tomislav Markovski", NULL}, {N_("Bokmål Norwegian"), "nb", "Hallvard Glad", "hallvard.glad@gmail.com"}, {N_("Bokmål Norwegian"), "nb", "Petter Johan Olsen", NULL}, diff -r 5c77b620375c -r 4491a662d527 pidgin/plugins/disco/gtkdisco.c --- a/pidgin/plugins/disco/gtkdisco.c Mon Nov 30 16:07:54 2009 +0900 +++ b/pidgin/plugins/disco/gtkdisco.c Thu Dec 17 16:50:12 2009 +0900 @@ -225,8 +225,8 @@ gtk_widget_set_sensitive(dialog->account_widget, FALSE); username = purple_account_get_username(dialog->account); - at = g_utf8_strchr(username, -1, '@'); - slash = g_utf8_strchr(username, -1, '/'); + at = strchr(username, '@'); + slash = strchr(username, '/'); if (at && !slash) { server = g_strdup_printf("%s", at + 1); } else if (at && slash && at + 1 < slash) { diff -r 5c77b620375c -r 4491a662d527 pidgin/win32/nsis/pidgin-installer.nsi --- a/pidgin/win32/nsis/pidgin-installer.nsi Mon Nov 30 16:07:54 2009 +0900 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Thu Dec 17 16:50:12 2009 +0900 @@ -191,6 +191,7 @@ !insertmacro MUI_LANGUAGE "Hungarian" !insertmacro MUI_LANGUAGE "Dutch" !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "NorwegianNynorsk" !insertmacro MUI_LANGUAGE "Polish" !insertmacro MUI_LANGUAGE "PortugueseBR" !insertmacro MUI_LANGUAGE "Portuguese" @@ -230,6 +231,7 @@ !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "KURDISH" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\kurdish.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "LITHUANIAN" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\lithuanian.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "NORWEGIAN" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\norwegian.nsh" + !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "NORWEGIANNYNORSK" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\norwegian_nynorsk.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "POLISH" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\polish.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "PORTUGUESE" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\portuguese.nsh" !insertmacro PIDGIN_MACRO_INCLUDE_LANGFILE "PORTUGUESEBR" "${PIDGIN_NSIS_INCLUDE_PATH}\translations\portuguese-br.nsh" diff -r 5c77b620375c -r 4491a662d527 pidgin/win32/nsis/translations/norwegian_nynorsk.nsh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/win32/nsis/translations/norwegian_nynorsk.nsh Thu Dec 17 16:50:12 2009 +0900 @@ -0,0 +1,80 @@ +;; +;; norwegian_nynorsk.nsh +;; +;; Norwegian nynorsk language strings for the Windows Pidgin NSIS installer. +;; Windows Code page: 1252 +;; +;; Version 3 + +; Startup Checks +!define INSTALLER_IS_RUNNING "Installasjonsprogrammet kjrer allereie." +!define PIDGIN_IS_RUNNING "Pidgin kjrer no. Lukk programmet og prv igjen." +!define GTK_INSTALLER_NEEDED "GTK+-kjremiljet manglar eller treng bli oppdatert.$\rInstaller v${GTK_MIN_VERSION} eller nyare av GTK+-kjremiljet" + +; License Page +!define PIDGIN_LICENSE_BUTTON "Neste >" +!define PIDGIN_LICENSE_BOTTOM_TEXT "$(^Name) blir utgjeve med ein GNU General Public License (GPL). Lisensen er berre gjeven her for opplysningsforml. $_CLICK" + +; Components Page +!define PIDGIN_SECTION_TITLE "Pidgin lynmeldingsklient (pkravd)" +!define GTK_SECTION_TITLE "GTK+-kjremilj (pkravd om det ikkje er til stades no)" +!define PIDGIN_SHORTCUTS_SECTION_TITLE "Snarvegar" +!define PIDGIN_DESKTOP_SHORTCUT_SECTION_TITLE "Skrivebordet" +!define PIDGIN_STARTMENU_SHORTCUT_SECTION_TITLE "Startmenyen" +!define PIDGIN_SECTION_DESCRIPTION "Pidgin programfiler og DLL-ar" +!define GTK_SECTION_DESCRIPTION "Ei grafisk brukargrensesnittverktykasse p fleire plattformer som Pidgin nyttar" + +!define PIDGIN_SHORTCUTS_SECTION_DESCRIPTION "Snarvegar for starta Pidgin" +!define PIDGIN_DESKTOP_SHORTCUT_DESC "Lag ein snarveg til Pidgin p skrivebordet" +!define PIDGIN_STARTMENU_SHORTCUT_DESC "Lag ein snarveg til Pidgin p startmenyen" + +; GTK+ Directory Page +!define GTK_UPGRADE_PROMPT "Fann ei gammal utgve av GTK+-kjremiljet. Vil du oppdatera ho?$\rMerk: $(^Name) vil kanskje ikkje fungera om du ikkje oppdaterer." +!define GTK_WINDOWS_INCOMPATIBLE "Windows 95/98/Me er ikkje kompatibelt med GTK+ 2.8.0 eller nyare. GTK+ ${GTK_INSTALL_VERSION} kjem ikkje til bli installert.$\rInstallasjonen vil bli abvroten om ikkje GTK+ ${GTK_MIN_VERSION} eller nyare allereie er installert." + +; Installer Finish Page +!define PIDGIN_FINISH_VISIT_WEB_SITE "Besk Pidgin si nettside" + +; Pidgin Section Prompts and Texts +!define PIDGIN_PROMPT_CONTINUE_WITHOUT_UNINSTALL "Klarte ikkje avinstallera Pidgin-utgva som er i bruk. Den nye utgva kjem til bli installert utan ta vekk den gjeldande." + +; GTK+ Section Prompts +!define GTK_INSTALL_ERROR "Klarte ikkje installera GTK+-kjremiljet." +!define GTK_BAD_INSTALL_PATH "Klarer ikkje laga eller f tilgang til bana du skreiv." + +; URL Handler section +!define URI_HANDLERS_SECTION_TITLE "URI-referanse" + +; Uninstall Section Prompts +!define un.PIDGIN_UNINSTALL_ERROR_1 "Avinstallasjonsprogrammet fann ikkje registerpostar for Pidgin.$\rTruleg har ein annan brukar installert denne applikasjonen." +!define un.PIDGIN_UNINSTALL_ERROR_2 "Du har ikkje lyve til kunna avinstallera denne applikasjonen." + +; Spellcheck Section Prompts +!define PIDGIN_SPELLCHECK_SECTION_TITLE "Stavekontrollhjelp" +!define PIDGIN_SPELLCHECK_ERROR "Klarte ikkje installera stavekontrollen" +!define PIDGIN_SPELLCHECK_DICT_ERROR "Klarte ikkje installera stavekontrollordlista" +!define PIDGIN_SPELLCHECK_SECTION_DESCRIPTION "Stavekontrollhjelp (treng internettsamband for installera)." +!define ASPELL_INSTALL_FAILED "Installasjonen feila" +!define PIDGIN_SPELLCHECK_BRETON "Bretonsk" +!define PIDGIN_SPELLCHECK_CATALAN "Katalansk" +!define PIDGIN_SPELLCHECK_CZECH "Tsjekkisk" +!define PIDGIN_SPELLCHECK_WELSH "Walisisk" +!define PIDGIN_SPELLCHECK_DANISH "Dansk" +!define PIDGIN_SPELLCHECK_GERMAN "Tysk" +!define PIDGIN_SPELLCHECK_GREEK "Gresk" +!define PIDGIN_SPELLCHECK_ENGLISH "Engelsk" +!define PIDGIN_SPELLCHECK_ESPERANTO "Esperanto" +!define PIDGIN_SPELLCHECK_SPANISH "Spansk" +!define PIDGIN_SPELLCHECK_FAROESE "Frysk" +!define PIDGIN_SPELLCHECK_FRENCH "Fransk" +!define PIDGIN_SPELLCHECK_ITALIAN "Italiensk" +!define PIDGIN_SPELLCHECK_DUTCH "Nederlandsk" +!define PIDGIN_SPELLCHECK_NORWEGIAN "Norsk" +!define PIDGIN_SPELLCHECK_POLISH "Polsk" +!define PIDGIN_SPELLCHECK_PORTUGUESE "Portugisisk" +!define PIDGIN_SPELLCHECK_ROMANIAN "Rumensk" +!define PIDGIN_SPELLCHECK_RUSSIAN "Russisk" +!define PIDGIN_SPELLCHECK_SLOVAK "Slovakisk" +!define PIDGIN_SPELLCHECK_SWEDISH "Svensk" +!define PIDGIN_SPELLCHECK_UKRAINIAN "Ukrainsk" + diff -r 5c77b620375c -r 4491a662d527 po/ChangeLog --- a/po/ChangeLog Mon Nov 30 16:07:54 2009 +0900 +++ b/po/ChangeLog Thu Dec 17 16:50:12 2009 +0900 @@ -1,5 +1,9 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.6.5 + * Norwegian Nynorsk win32 translation added (Yngve Spjeld Landro) + * Russian translation updated (Антон Самохвалов) + version 2.6.4 * Afrikaans translation updated (Friedel Wolff) * Chinese (Hong Kong) translation updated (Ambrose C. Li, Paladin R. Liu) diff -r 5c77b620375c -r 4491a662d527 po/POTFILES.in --- a/po/POTFILES.in Mon Nov 30 16:07:54 2009 +0900 +++ b/po/POTFILES.in Thu Dec 17 16:50:12 2009 +0900 @@ -88,6 +88,10 @@ libpurple/protocols/irc/parse.c libpurple/protocols/jabber/adhoccommands.c libpurple/protocols/jabber/auth.c +libpurple/protocols/jabber/auth_cyrus.c +libpurple/protocols/jabber/auth_digest_md5.c +libpurple/protocols/jabber/auth_plain.c +libpurple/protocols/jabber/auth_scram.c libpurple/protocols/jabber/bosh.c libpurple/protocols/jabber/buddy.c libpurple/protocols/jabber/chat.c diff -r 5c77b620375c -r 4491a662d527 po/de.po --- a/po/de.po Mon Nov 30 16:07:54 2009 +0900 +++ b/po/de.po Thu Dec 17 16:50:12 2009 +0900 @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-11-26 22:45+0100\n" -"PO-Revision-Date: 2009-11-26 22:44+0100\n" +"POT-Creation-Date: 2009-12-10 22:38+0100\n" +"PO-Revision-Date: 2009-12-11 20:55+0100\n" "Last-Translator: Björn Voigt \n" "Language-Team: Deutsch \n" "MIME-Version: 1.0\n" @@ -1564,6 +1564,7 @@ msgid "Online" msgstr "Online" +#. primative, no, id, name msgid "Offline" msgstr "Offline" @@ -2516,7 +2517,7 @@ msgstr "Wenn Benutzer so viele Minuten nicht gesprochen hat" msgid "Apply hiding rules to buddies" -msgstr "Regeln zum Verstecken auf Buddies anwenden" +msgstr "Regeln zum Verstecken auf Buddys anwenden" #. *< type #. *< ui_requirement @@ -3106,7 +3107,7 @@ msgstr "Passwort (nochmal)" msgid "Enter captcha text" -msgstr "Captcha-Text eigeben" +msgstr "Captcha-Text eingeben" msgid "Captcha" msgstr "Captcha" @@ -3187,10 +3188,12 @@ msgid "Add to chat..." msgstr "Zum Chat hinzufügen..." +#. 0 #. Global msgid "Available" msgstr "Verfügbar" +#. 1 #. get_yahoo_status_from_purple_status() returns YAHOO_STATUS_CUSTOM for #. * the generic away state (YAHOO_STATUS_TYPE_AWAY) with no message #. Away stuff @@ -3824,6 +3827,13 @@ "Der Server erfordert eine Klartext-Authentifizierung über einen " "unverschlüsselten Kanal" +#. This should never happen! +msgid "Invalid response from server" +msgstr "Ungültige Serverantwort" + +msgid "Server does not use any supported authentication method" +msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" + #, c-format msgid "" "%s requires plaintext authentication over an unencrypted connection. Allow " @@ -3836,15 +3846,6 @@ msgid "Plaintext Authentication" msgstr "Klartext-Authentifizierung" -msgid "SASL authentication failed" -msgstr "SASL-Authentifizierung fehlgeschlagen" - -msgid "Invalid response from server" -msgstr "Ungültige Serverantwort" - -msgid "Server does not use any supported authentication method" -msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" - msgid "You require encryption, but it is not available on this server." msgstr "" "Sie fordern Verschlüsselung, aber diese ist auf dem Server nicht verfügbar." @@ -3852,10 +3853,32 @@ msgid "Invalid challenge from server" msgstr "Ungültige Challenge vom Server" +msgid "Server thinks authentication is complete, but client does not" +msgstr "" + +msgid "SASL authentication failed" +msgstr "SASL-Authentifizierung fehlgeschlagen" + #, c-format msgid "SASL error: %s" msgstr "SASL-Fehler: %s" +#, fuzzy +msgid "Unable to canonicalize username" +msgstr "Kann nicht konfigurieren" + +#, fuzzy +msgid "Unable to canonicalize password" +msgstr "Es konnte kein lauschender Port geöffnet werden." + +#, fuzzy +msgid "Malicious challenge from server" +msgstr "Ungültige Challenge vom Server" + +#, fuzzy +msgid "Unexpected response from server" +msgstr "Ungültige HTTP-Antwort vom Server empfangen" + msgid "The BOSH connection manager terminated your session." msgstr "Der BOSH-Verbindungsmanager hat Ihre Sitzung beendet." @@ -3956,13 +3979,21 @@ msgid "Resource" msgstr "Ressource" +#, fuzzy +msgid "Uptime" +msgstr "Aktualisieren" + +#, c-format +msgid "%s" +msgstr "" + +msgid "Logged Off" +msgstr "Abgemeldet" + #, c-format msgid "%s ago" msgstr "vor %s" -msgid "Logged Off" -msgstr "Abgemeldet" - msgid "Middle Name" msgstr "Zweiter Name" @@ -4011,9 +4042,6 @@ msgid "Log Out" msgstr "Abmelden" -#. primative, no, id, name -#. 0 -#. 1 #. 2 msgid "Chatty" msgstr "Gesprächig" @@ -4021,6 +4049,7 @@ msgid "Extended Away" msgstr "Abwesend (erweitert)" +#. 3 msgid "Do Not Disturb" msgstr "Nicht stören" @@ -4161,7 +4190,7 @@ "Unable to find alternative XMPP connection methods after failing to connect " "directly." msgstr "" -"Nach dem Fehlschlagen einer direkten XMPP-Verbindung konnen keine " +"Nach dem Fehlschlagen einer direkten XMPP-Verbindung können keine " "alternativen Verbindungsmethoden gefunden werden." msgid "Invalid XMPP ID" @@ -4459,7 +4488,7 @@ msgstr "Ungültige ID" msgid "Invalid Namespace" -msgstr "Ungültiger Namenraum" +msgstr "Ungültiger Namensraum" msgid "Invalid XML" msgstr "Ungültiges XML" @@ -5875,7 +5904,7 @@ msgstr "Hier können Sie Ihr MXit-Profil aktualisieren" msgid "View Splash" -msgstr "Startbildschirn anschauen" +msgstr "Startbildschirm anschauen" msgid "There is no splash-screen currently available" msgstr "Es gibt gerade keinen Startbildschirm" @@ -6315,7 +6344,7 @@ msgstr "Bildschirmauflösung (dpi)" msgid "Base font size (points)" -msgstr "Basis-Schriftgrüße (Punkt)" +msgstr "Basis-Schriftgröße (Punkt)" msgid "User" msgstr "Benutzer" @@ -6888,7 +6917,7 @@ msgstr "Dienst nicht definiert" msgid "Obsolete SNAC" -msgstr "Obsoleteter SNAC" +msgstr "Obsoleter SNAC" msgid "Not supported by host" msgstr "Nicht unterstützt vom Host" @@ -7993,7 +8022,7 @@ #, c-format msgid "%u needs authorization" -msgstr "%u benötigt Authorisierung" +msgstr "%u benötigt Autorisierung" msgid "Add buddy authorize" msgstr "Buddy-Autorisierung hinzufügen" @@ -8678,8 +8707,8 @@ "The identifier '%s' may possibly refer to any of the following users. Please " "select the correct user from the list below to add them to your buddy list." msgstr "" -"Der Bezeichner '%s' kann sich vielleicht zu einem der folgenden Benutzer " -"beziehen. Bitte wählen Sie den korrekten Benutzer vor der untenstehenden " +"Der Bezeichner '%s' kann sich vielleicht auf einen der folgenden Benutzer " +"beziehen. Bitte wählen Sie den korrekten Benutzer aus der untenstehenden " "Liste, um ihn zu Ihrer Buddy-Liste hinzuzufügen." msgid "Select User" @@ -8923,7 +8952,7 @@ #, c-format msgid "The %s buddy is not trusted" -msgstr "Dem Buddy %s wird nicht (kryptografisch) vertraut" +msgstr "Dem Buddy %s wird nicht (kryptographisch) vertraut" msgid "" "You cannot receive buddy notifications until you import his/her public key. " @@ -9902,7 +9931,7 @@ #, c-format msgid "Failure: Remote does not support proposed cipher" msgstr "" -"Fehler: Entferntes Programm unterstützt die vorgeschlagene Cipher nicht" +"Fehler: Entferntes Programm unterstützt die vorgeschlagene Chiffre nicht" #, c-format msgid "Failure: Remote does not support proposed PKCS" @@ -12221,14 +12250,7 @@ "
We can't help with 3rd party protocols or plugins!
This list's " "primary language is English. You are welcome to post in another " "language, but the responses may be less helpful.

" -msgstr "" -"Hilfe von anderen Pidgin-Benutzern: support@pidgin.im
Dies ist eine öffentliche Mailing-Liste! (Archiv)
Wir können nicht bei Problemen mit Drittanbieter-Protokollen oder " -"Plugins helfen!
Die Hauptsprache dieser Liste ist Englisch. Sie " -"können gern in einer anderen Sprache schreiben, aber die Antworten könnten " -"weniger hilfreich sein.

" +msgstr "Hilfe von anderen Pidgin-Benutzern:
support@pidgin.im
Dies ist eine öffentliche Mailing-Liste! (Archiv)
Wir können nicht bei Problemen mit Drittanbieter-Protokollen oder Plugins helfen!
Die Hauptsprache dieser Liste ist Englisch. Sie können gern in einer anderen Sprache schreiben, aber die Antworten könnten weniger hilfreich sein.
Deutschsprachige Benutzer können auch das Portal Pidgin-IM.de nutzen. Dort finden Sie aktuelle Informationen zu Pidgin, können mit anderen Benutzern im Forum diskutieren und Hilfe zu Problemen finden. Beachten Sie, dass dieses Portal unabhängig vom offiziellen Pidgin-Projekt ist.

" #, c-format msgid "" @@ -14326,17 +14348,17 @@ "haben." msgid "Markerline" -msgstr "Markierunglinie" +msgstr "Markierungslinie" msgid "Draw a line to indicate new messages in a conversation." msgstr "" "Eine Linie zeichnen um neue Nachrichten in einer Unterhaltung anzuzeigen." msgid "Jump to markerline" -msgstr "Springen zur Markierunglinie" +msgstr "Springen zur Markierungslinie" msgid "Draw Markerline in " -msgstr "Zeichne eine Markierunglinie in " +msgstr "Zeichne eine Markierungslinie in " msgid "_IM windows" msgstr "_IM-Fenster" @@ -14816,7 +14838,7 @@ msgstr "Zeigt die Buddy-Liste als tickerähnliche Laufschrift." msgid "Display Timestamps Every" -msgstr "Zeige Zeitstemple alle" +msgstr "Zeige Zeitstempel alle" #. *< type #. *< ui_requirement diff -r 5c77b620375c -r 4491a662d527 po/ru.po --- a/po/ru.po Mon Nov 30 16:07:54 2009 +0900 +++ b/po/ru.po Thu Dec 17 16:50:12 2009 +0900 @@ -10,7 +10,7 @@ msgstr "" "Project-Id-Version: ru\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-11-29 20:30-0500\n" +"POT-Creation-Date: 2009-12-05 19:57-0800\n" "PO-Revision-Date: 2008-05-14 15:00+0400\n" "Last-Translator: Антон Самохвалов \n" "Language-Team: \n" @@ -1540,7 +1540,7 @@ msgstr "TinyURL для вышеуказанного: %s" msgid "Please wait while TinyURL fetches a shorter URL ..." -msgstr "" +msgstr "Пожалуйста, подождите пока TinyURL получает короткий адрес ..." msgid "Only create TinyURL for URLs of this length or greater" msgstr "Создайте TinyURL для адресов только с такой длиной или больше" @@ -3830,6 +3830,13 @@ msgstr "" "Сервер требует аутентификацию простым текстом через нешифрованный поток" +#. This should never happen! +msgid "Invalid response from server" +msgstr "Неверный отклик от сервера" + +msgid "Server does not use any supported authentication method" +msgstr "Сервер не использует ни одного поддерживаемого метода аутентификации" + #, c-format msgid "" "%s requires plaintext authentication over an unencrypted connection. Allow " @@ -3841,25 +3848,38 @@ msgid "Plaintext Authentication" msgstr "Аутентификация простым текстом" -msgid "SASL authentication failed" -msgstr "Аутентификация SASL провалилась" - -msgid "Invalid response from server" -msgstr "Неверный отклик от сервера" - -msgid "Server does not use any supported authentication method" -msgstr "Сервер не использует ни одного поддерживаемого метода аутентификации" - msgid "You require encryption, but it is not available on this server." msgstr "Вы запросили шифрование, но оно недоступно на сервере." msgid "Invalid challenge from server" msgstr "Неверный запрос с сервера" +msgid "Server thinks authentication is complete, but client does not" +msgstr "" + +msgid "SASL authentication failed" +msgstr "Аутентификация SASL провалилась" + #, c-format msgid "SASL error: %s" msgstr "Ошибка SASL: %s" +#, fuzzy +msgid "Unable to canonicalize username" +msgstr "Не удаётся настроить" + +#, fuzzy +msgid "Unable to canonicalize password" +msgstr "Не удалось открыть прослушиваемый порт." + +#, fuzzy +msgid "Malicious challenge from server" +msgstr "Неверный запрос с сервера" + +#, fuzzy +msgid "Unexpected response from server" +msgstr "Получен непредвиденный HTTP-отклик от сервера." + msgid "The BOSH connection manager terminated your session." msgstr "Управление соединениями BOSH прервало ваш сеанс." @@ -3959,13 +3979,21 @@ msgid "Resource" msgstr "Ресурс" +#, fuzzy +msgid "Uptime" +msgstr "Обновить" + +#, c-format +msgid "%s" +msgstr "" + +msgid "Logged Off" +msgstr "Вышел из сети" + #, c-format msgid "%s ago" msgstr "%s назад" -msgid "Logged Off" -msgstr "Вышел из сети" - msgid "Middle Name" msgstr "Отчество" @@ -5818,45 +5846,37 @@ msgstr "Ваше текущее настроение" #. add all moods to list -#, fuzzy msgid "New Mood" -msgstr "Настроение пользователя" - -#, fuzzy +msgstr "Новое настроение" + msgid "Change your Mood" -msgstr "Изменить пароль" - -#, fuzzy +msgstr "Изменить ваше настроение" + msgid "How do you feel right now?" -msgstr "Прямо сейчас меня здесь нет" - -#, fuzzy +msgstr "Как вы сейчас?" + msgid "The PIN you entered is invalid." -msgstr "Введённый ключ SecurID неверный." - -#, fuzzy +msgstr "Введённый PIN-код неправильный." + msgid "The PIN you entered has an invalid length [4-10]." -msgstr "Введённый ключ SecurID неверный." +msgstr "Введённый PIN-код неправильной длины [4-10]." msgid "The PIN is invalid. It should only consist of digits [0-9]." -msgstr "" - -#, fuzzy +msgstr "PIN-код неправильный. Он должен содержать только цифры [0-9]." + msgid "The two PINs you entered do not match." -msgstr "Новые пароли не совпадают." - -#, fuzzy +msgstr "Два введённых PIN-кода не совпадают." + msgid "The name you entered is invalid." -msgstr "Введённый ключ SecurID неверный." +msgstr "Введённое имя неправильное." msgid "" "The birthday you entered is invalid. The correct format is: 'YYYY-MM-DD'." -msgstr "" +msgstr "Введённая вами дата рождения неправильная. Формат такой: 'ГГГГ-ММ-ДД'." #. show error to user -#, fuzzy msgid "Profile Update Error" -msgstr "Ошибка записи" +msgstr "Ошибка Обновления Профиля" #. no profile information yet, so we cannot update #. (reference: "libpurple/request.h") @@ -5865,89 +5885,80 @@ msgid "Your profile information is not yet retrieved. Please try again later." msgstr "" +"Информация о вашем профиле ещё не получена. Пожалуйста, попробуйте позже." #. pin -#, fuzzy msgid "PIN" -msgstr "UIN" +msgstr "PIN" msgid "Verify PIN" -msgstr "" +msgstr "Проверьте PIN" #. display name -#, fuzzy msgid "Display Name" -msgstr "Фамилия" +msgstr "Показать имя" #. hidden msgid "Hide my number" -msgstr "" +msgstr "Скрыть мой номер" #. mobile number -#, fuzzy msgid "Mobile Number" -msgstr "Номер мобильного телефона" - -#, fuzzy +msgstr "Номер мобильного" + msgid "Update your Profile" -msgstr "Профиль пользователя" +msgstr "Обновить ваш профиль" msgid "Here you can update your MXit profile" -msgstr "" +msgstr "Здесь вы можете обновить ваш MXit-профиль" msgid "View Splash" -msgstr "" +msgstr "Показать заставку" msgid "There is no splash-screen currently available" -msgstr "" - -#, fuzzy +msgstr "Сейчас нет доступных экранов-заставок" + msgid "About" -msgstr "Обо мне" +msgstr "Про" #. display / change mood -#, fuzzy msgid "Change Mood..." -msgstr "Изменить пароль..." +msgstr "Изменить настроение..." #. display / change profile -#, fuzzy msgid "Change Profile..." -msgstr "Изменить пароль..." +msgstr "Изменить профиль..." #. display splash-screen -#, fuzzy msgid "View Splash..." -msgstr "Просмотреть журнал..." +msgstr "Показать заставку..." #. display plugin version -#, fuzzy msgid "About..." -msgstr "Обо мне" +msgstr "О модуле..." #. the file is too big -#, fuzzy msgid "The file you are trying to send is too large!" -msgstr "Сообщение слишком велико." +msgstr "Файл, которые вы пытаетесь отправить слишком велик!" msgid "" "Unable to connect to the MXit HTTP server. Please check your server settings." msgstr "" - -#, fuzzy +"Не удаётся соединиться с сервером HTTP MXit. Пожалуйста, проверьте ваши " +"настройки сервера." + msgid "Logging In..." -msgstr "Входит в сеть" +msgstr "Входит в сеть..." #, fuzzy msgid "" "Unable to connect to the MXit server. Please check your server settings." msgstr "" -"Не удаётся соединиться с сервером. Введите адрес сервера, с которым вы " -"хотите соединиться." - -#, fuzzy +"Не удаётся соединиться с сервером MXit. Пожалуйста, проверьте ваши настройки " +"сервера." + msgid "Connecting..." -msgstr "Соединение" +msgstr "Соединение..." #, fuzzy msgid "The nick name you entered is invalid." @@ -5959,142 +5970,126 @@ #. mxit login name msgid "MXit Login Name" -msgstr "" +msgstr "Имя входа MXit" #. nick name -#, fuzzy msgid "Nick Name" msgstr "Псевдоним" #. show the form to the user to complete #, fuzzy msgid "Register New MXit Account" -msgstr "Зарегистрировать новую учётную запись XMPP" - -#, fuzzy +msgstr "Зарегистрировать новую учётную запись MXit" + msgid "Please fill in the following fields:" -msgstr "Заполните следующие поля" +msgstr "Пожалуйста, заполните следующие поля:" #. no reply from the WAP site msgid "Error contacting the MXit WAP site. Please try again later." -msgstr "" +msgstr "Ошибка связи с сайтом MXit WAP. Пожалуйста, попробуйте позже." #. wapserver error #. server could not find the user msgid "" "MXit is currently unable to process the request. Please try again later." -msgstr "" +msgstr "MXit не может сейчас обработать апрос. Пожалуйста, попробуйте позже." msgid "Wrong security code entered. Please try again later." -msgstr "" +msgstr "Введён ошибочный код безопасности. Пожалуйста, попробуйте позже." msgid "Your session has expired. Please try again later." -msgstr "" +msgstr "Ваш сеанс истёк. Пожалуйста, попробуйте позже." msgid "Invalid country selected. Please try again." -msgstr "" +msgstr "Выбрана неправильная страна. Пожалуйста, попробуйте ещё раз." msgid "Username is not registered. Please register first." msgstr "" +"Имя пользователя не зарегистрировано. Пожалуйста, сначала зарегистрируйтесь." msgid "Username is already registered. Please choose another username." msgstr "" - -#, fuzzy +"Имя пользователя уже зарегистрировано. Пожалуйста, выберите другое имя." + msgid "Internal error. Please try again later." -msgstr "Сервер недоступен; повторите попытку позже" +msgstr "Внутренняя ошибка. Пожалуйста, попробуйте позже." msgid "You did not enter the security code" -msgstr "" - -#, fuzzy +msgstr "Вы не ввели код безопасности" + msgid "Security Code" -msgstr "Безопасность включена" +msgstr "Код безопасности" #. ask for input -#, fuzzy msgid "Enter Security Code" -msgstr "Введите код" - -#, fuzzy +msgstr "Введите код безопасности" + msgid "Your Country" -msgstr "Страна" - -#, fuzzy +msgstr "Ваша страна" + msgid "Your Language" -msgstr "Предпочитаемый язык" +msgstr "Ваш язык" #. display the form to the user and wait for his/her input -#, fuzzy msgid "MXit Authorization" -msgstr "Требовать авторизацию" +msgstr "Авторизация MXit" msgid "MXit account validation" -msgstr "" - -#, fuzzy +msgstr "Подтверждение учётной записи MXit" + msgid "Retrieving User Information..." -msgstr "Информация о сервере" +msgstr "Получение информации о пользователе..." msgid "Loading menu..." -msgstr "" - -#, fuzzy +msgstr "Загрузка меню..." + msgid "Status Message" -msgstr "Отправленные сообщения" - -#, fuzzy +msgstr "Сообщения состояния" + msgid "Hidden Number" -msgstr "Отчество" - -#, fuzzy +msgstr "Скрытый номер" + msgid "Your Mobile Number..." -msgstr "Ввести номер мобильного телефона..." +msgstr "Номер мобильного телефона..." #. Configuration options #. WAP server (reference: "libpurple/accountopt.h") -#, fuzzy msgid "WAP Server" -msgstr "Сервер" - -#, fuzzy +msgstr "WAP-сервер" + msgid "Connect via HTTP" -msgstr "Соединение по TCP" +msgstr "Соединение через HTTP" msgid "Enable splash-screen popup" -msgstr "" +msgstr "Включить контекстное меню экрана-заставки" #. we must have lost the connection, so terminate it so that we can reconnect msgid "We have lost the connection to MXit. Please reconnect." -msgstr "" +msgstr "Мы потеряли соединение с MXit. Пожалуйста, пересоединитесь." #. packet could not be queued for transmission -#, fuzzy msgid "Message Send Error" -msgstr "Ошибка сообщения XMPP" - -#, fuzzy +msgstr "Ошибка отправки сообщения" + msgid "Unable to process your request at this time" -msgstr "Не удаётся соединиться с сервером." +msgstr "Не удаётся сейчас обработать ваш запрос" msgid "Timeout while waiting for a response from the MXit server." -msgstr "" - -#, fuzzy +msgstr "Истекло время во время ожидания ответа от сервера MXit." + msgid "Successfully Logged In..." -msgstr "Успешно подсоединён Qun" +msgstr "Успешно вошёл в сеть..." #, fuzzy, c-format msgid "" "%s sent you an encrypted message, but it is not supported on this client." msgstr "%s послал вам приглашение к веб-камере, которое ещё не поддерживается." -#, fuzzy msgid "Message Error" -msgstr "Ошибка сообщения XMPP" +msgstr "Сообщение об ошибке" msgid "Cannot perform redirect using the specified protocol" -msgstr "" +msgstr "Не могу осуществить перенаправление используя указанный протокол" #, fuzzy msgid "An internal MXit server error occurred." @@ -6158,53 +6153,52 @@ #. bad packet msgid "Invalid packet received from MXit." -msgstr "" +msgstr "Получен неправильный пакет от MXit." #. connection error msgid "A connection error occurred to MXit. (read stage 0x01)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x01)" #. connection closed msgid "A connection error occurred to MXit. (read stage 0x02)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x02)" msgid "A connection error occurred to MXit. (read stage 0x03)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x03)" #. malformed packet length record (too long) msgid "A connection error occurred to MXit. (read stage 0x04)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x04)" #. connection error msgid "A connection error occurred to MXit. (read stage 0x05)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x05)" #. connection closed msgid "A connection error occurred to MXit. (read stage 0x06)" -msgstr "" +msgstr "Возникла ошибка соединения с MXit. (этап чтения 0x06)" #, fuzzy msgid "Pending" -msgstr "Отправка" +msgstr "Рассмотрение" #, fuzzy msgid "Invited" -msgstr "Пригласить" +msgstr "Приглашено" #, fuzzy msgid "Rejected" -msgstr "Отвергнуть" +msgstr "Отклонено" #, fuzzy msgid "Deleted" -msgstr "Удалить" +msgstr "Удалено" msgid "MXit Advertising" -msgstr "" - -#, fuzzy +msgstr "Реклама MXit" + msgid "More Information" -msgstr "Информация о работе" +msgstr "Больше информации" #, c-format msgid "No such user: %s" @@ -13354,20 +13348,17 @@ "Выберите тему смайликов, которую вы хотели бы использовать. Новые темы могут " "быть установлены перетаскиванием их в список тем." -#, fuzzy msgid "Buddy List Theme:" -msgstr "Тема Списка собеседников" - -#, fuzzy +msgstr "Тема Списка собеседников:" + msgid "Status Icon Theme:" -msgstr "Статус для %s" +msgstr "Тема значков состояния:" msgid "Sound Theme:" -msgstr "" - -#, fuzzy +msgstr "Звуковая тема:" + msgid "Smiley Theme:" -msgstr "Темы смайликов" +msgstr "Тема смайликов:" msgid "Keyboard Shortcuts" msgstr "Горячие клавиши" @@ -13385,9 +13376,8 @@ msgid "On unread messages" msgstr "При наличии непрочитанных сообщений" -#, fuzzy msgid "Conversation Window" -msgstr "Окна бесед" +msgstr "Окно беседы" msgid "_Hide new IM conversations:" msgstr "_Скрывать новые беседы:" @@ -13481,21 +13471,18 @@ msgid "Cannot start proxy configuration program." msgstr "Не могу запустить программу настройки прокси." -#, fuzzy msgid "Cannot start browser configuration program." msgstr "Не могу запустить программу настройки веб-проводника." -#, fuzzy msgid "Disabled" msgstr "Отключено" -#, fuzzy, c-format +#, c-format msgid "Use _automatically detected IP address: %s" -msgstr "Использовать _автоматически обнаруженный IP-адрес: %s" - -#, fuzzy +msgstr "И_спользовать автоматически определённый IP-адрес: %s" + msgid "ST_UN server:" -msgstr "ST_UN сервер:" +msgstr "_STUN сервер:" msgid "Example: stunserver.org" msgstr "Пример: stunserver.org" @@ -13519,7 +13506,7 @@ #, fuzzy msgid "_End:" -msgstr "Р_азвернуть" +msgstr "Коне_ц:" #. TURN server msgid "Relay Server (TURN)" @@ -13527,15 +13514,15 @@ #, fuzzy msgid "_TURN server:" -msgstr "ST_UN-сервер:" +msgstr "_TURN-сервер:" #, fuzzy msgid "Use_rname:" -msgstr "Имя пользователя:" +msgstr "Им_я пользователя:" #, fuzzy msgid "Pass_word:" -msgstr "Пароль:" +msgstr "Паро_ль:" msgid "Seamonkey" msgstr "Seamonkey"