diff libpurple/protocols/jabber/jabber.c @ 30909:25e200cb3532

propagate from branch 'im.pidgin.pidgin' (head dca20e09164518b0f7a773edd7e9649f41d8873d) to branch 'im.pidgin.cpw.malu.xmpp.google_refactor' (head 7823c556f2d1bbca43f5ef9f0fea30cabc5d313f)
author Marcus Lundblad <ml@update.uu.se>
date Tue, 31 Aug 2010 18:28:10 +0000
parents ae615b3d3e47 8084631e2e84
children 356a8d472395
line wrap: on
line diff
--- a/libpurple/protocols/jabber/jabber.c	Fri Apr 09 22:14:51 2010 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Aug 31 18:28:10 2010 +0000
@@ -73,6 +73,10 @@
 #include "jingle/rtp.h"
 
 #define PING_TIMEOUT 60
+/* Send a whitespace keepalive to the server if we haven't sent
+ * anything in the last 120 seconds
+ */
+#define DEFAULT_INACTIVITY_TIME 120
 
 GList *jabber_features = NULL;
 GList *jabber_identities = NULL;
@@ -168,6 +172,8 @@
 		char *msg = jabber_parse_error(js, packet, &reason);
 		purple_connection_error_reason(js->gc, reason, msg);
 		g_free(msg);
+
+		return;
 	}
 
 	jabber_session_init(js);
@@ -201,19 +207,55 @@
 	 * resource string from being unreasonably long on systems which stuff the
 	 * whole FQDN in the hostname */
 	if((dot = strchr(hostname, '.')))
-			dot = '\0';
+		*dot = '\0';
 
 	return purple_strreplace(input, "__HOSTNAME__", hostname);
 }
 
+static gboolean
+jabber_process_starttls(JabberStream *js, xmlnode *packet)
+{
+	PurpleAccount *account;
+	xmlnode *starttls;
+
+	account = purple_connection_get_account(js->gc);
+
+	if(purple_ssl_is_supported()) {
+		jabber_send_raw(js,
+				"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
+		return TRUE;
+	}
+
+	starttls = xmlnode_get_child(packet, "starttls");
+	if(xmlnode_get_child(starttls, "required")) {
+		purple_connection_error_reason(js->gc,
+				PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+				_("Server requires TLS/SSL, but no TLS/SSL support was found."));
+		return TRUE;
+	}
+
+	if (g_str_equal("require_tls", purple_account_get_string(account, "connection_security", 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."));
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
 void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
 {
-	if(xmlnode_get_child(packet, "starttls")) {
-		if(jabber_process_starttls(js, packet)) {
+	PurpleAccount *account = purple_connection_get_account(js->gc);
+	const char *connection_security =
+		purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS);
+
+	if (xmlnode_get_child(packet, "starttls")) {
+		if (jabber_process_starttls(js, packet)) {
 			jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
 			return;
 		}
-	} else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) {
+	} else if (g_str_equal(connection_security, "require_tls") && !jabber_stream_is_ssl(js)) {
 		purple_connection_error_reason(js->gc,
 			 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
 			_("You require encryption, but it is not available on this server."));
@@ -360,8 +402,10 @@
 	int ret;
 	gboolean success = TRUE;
 
-	if (len == -1)
-		len = strlen(data);
+	g_return_val_if_fail(len > 0, FALSE);
+
+	if (js->state == JABBER_STREAM_CONNECTED)
+		jabber_stream_restart_inactivity_timer(js);
 
 	if (js->writeh == 0)
 		ret = jabber_do_send(js, data, len);
@@ -402,6 +446,12 @@
 
 void jabber_send_raw(JabberStream *js, const char *data, int len)
 {
+	PurpleConnection *gc;
+	PurpleAccount *account;
+
+	gc = js->gc;
+	account = purple_connection_get_account(gc);
+
 	/* because printing a tab to debug every minute gets old */
 	if(strcmp(data, "\t")) {
 		const char *username;
@@ -429,9 +479,9 @@
 			*data_start = '\0';
 		}
 
-		username = purple_connection_get_display_name(js->gc);
+		username = purple_connection_get_display_name(gc);
 		if (!username)
-			username = purple_account_get_username(purple_connection_get_account(js->gc));
+			username = purple_account_get_username(account);
 
 		purple_debug_misc("jabber", "Sending%s (%s): %s%s%s\n",
 				jabber_stream_is_ssl(js) ? " (ssl)" : "", username,
@@ -442,10 +492,13 @@
 		g_free(text);
 	}
 
-	purple_signal_emit(purple_connection_get_prpl(js->gc), "jabber-sending-text", js->gc, &data);
+	purple_signal_emit(purple_connection_get_prpl(gc), "jabber-sending-text", gc, &data);
 	if (data == NULL)
 		return;
 
+	if (len == -1)
+		len = strlen(data);
+
 	/* If we've got a security layer, we need to encode the data,
 	 * splitting it on the maximum buffer length negotiated */
 #ifdef HAVE_CYRUS_SASL
@@ -453,21 +506,36 @@
 		int pos = 0;
 
 		if (!js->gsc && js->fd<0)
-			return;
-
-		if (len == -1)
-			len = strlen(data);
+			g_return_if_reached();
 
 		while (pos < len) {
 			int towrite;
 			const char *out;
 			unsigned olen;
+			int rc;
 
 			towrite = MIN((len - pos), js->sasl_maxbuf);
 
-			sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
+			rc = sasl_encode(js->sasl, &data[pos], towrite,
+			                 &out, &olen);
+			if (rc != SASL_OK) {
+				gchar *error =
+					g_strdup_printf(_("SASL error: %s"),
+						sasl_errdetail(js->sasl));
+				purple_debug_error("jabber",
+					"sasl_encode error %d: %s\n", rc,
+					sasl_errdetail(js->sasl));
+				purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					error);
+				g_free(error);
+				return;
+			}
 			pos += towrite;
 
+			/* do_jabber_send_raw returns FALSE when it throws a
+			 * connection error.
+			 */
 			if (!do_jabber_send_raw(js, out, olen))
 				break;
 		}
@@ -475,9 +543,6 @@
 	}
 #endif
 
-	if (len == -1)
-		len = strlen(data);
-
 	if (js->bosh)
 		jabber_bosh_connection_send_raw(js->bosh, data);
 	else
@@ -486,7 +551,14 @@
 
 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len)
 {
-	JabberStream *js = (JabberStream*)gc->proto_data;
+	JabberStream *js = purple_connection_get_protocol_data(gc);
+
+	g_return_val_if_fail(js != NULL, -1);
+	/* TODO: It's probably worthwhile to restrict this to when the account
+	 * state is CONNECTED, but I can /almost/ envision reasons for wanting
+	 * to do things during the connection process.
+	 */
+
 	jabber_send_raw(js, buf, len);
 	return len;
 }
@@ -501,8 +573,7 @@
 	if (NULL == packet)
 		return;
 
-	if (!PURPLE_CONNECTION_IS_VALID(pc))
-		return;
+	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(pc));
 
 	js = purple_connection_get_protocol_data(pc);
 
@@ -559,7 +630,7 @@
 	/* TODO: It should be possible to make this check unnecessary */
 	if(!PURPLE_CONNECTION_IS_VALID(gc)) {
 		purple_ssl_close(gsc);
-		return;
+		g_return_if_reached();
 	}
 
 	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
@@ -590,24 +661,35 @@
 jabber_recv_cb(gpointer data, gint source, PurpleInputCondition condition)
 {
 	PurpleConnection *gc = data;
-	JabberStream *js = gc->proto_data;
+	JabberStream *js = purple_connection_get_protocol_data(gc);
 	int len;
 	static char buf[4096];
 
-	if(!PURPLE_CONNECTION_IS_VALID(gc))
-		return;
+	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
 
 	if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
 		gc->last_received = time(NULL);
 #ifdef HAVE_CYRUS_SASL
-		if (js->sasl_maxbuf>0) {
+		if (js->sasl_maxbuf > 0) {
 			const char *out;
 			unsigned int olen;
-			sasl_decode(js->sasl, buf, len, &out, &olen);
-			if (olen>0) {
+			int rc;
+
+			rc = sasl_decode(js->sasl, buf, len, &out, &olen);
+			if (rc != SASL_OK) {
+				gchar *error =
+					g_strdup_printf(_("SASL error: %s"),
+						sasl_errdetail(js->sasl));
+				purple_debug_error("jabber",
+					"sasl_decode_error %d: %s\n", rc,
+					sasl_errdetail(js->sasl));
+				purple_connection_error_reason(gc,
+					PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					error);
+			} else if (olen > 0) {
 				purple_debug_info("jabber", "RecvSASL (%u): %s\n", olen, out);
-				jabber_parser_process(js,out,olen);
-				if(js->reinit)
+				jabber_parser_process(js, out, olen);
+				if (js->reinit)
 					jabber_stream_init(js);
 			}
 			return;
@@ -643,7 +725,7 @@
 	/* TODO: It should be possible to make this check unnecessary */
 	if(!PURPLE_CONNECTION_IS_VALID(gc)) {
 		purple_ssl_close(gsc);
-		return;
+		g_return_if_reached();
 	}
 
 	js = gc->proto_data;
@@ -738,8 +820,7 @@
 	JabberStream *js;
 
 	/* If the connection is already disconnected, we don't need to do anything else */
-	if(!PURPLE_CONNECTION_IS_VALID(gc))
-		return;
+	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
 
 	js = gc->proto_data;
 	js->gsc = NULL;
@@ -842,6 +923,16 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID"));
+		g_free(user);
+		/* Destroying the connection will free the JabberStream */
+		return NULL;
+	}
+
+	if (!js->user->node || *(js->user->node) == '\0') {
+		purple_connection_error_reason(gc,
+			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
+			_("Invalid XMPP ID. Username portion must be set."));
+		g_free(user);
 		/* Destroying the connection will free the JabberStream */
 		return NULL;
 	}
@@ -850,6 +941,7 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Domain must be set."));
+		g_free(user);
 		/* Destroying the connection will free the JabberStream */
 		return NULL;
 	}
@@ -878,6 +970,7 @@
 	js->write_buffer = purple_circ_buffer_new(512);
 	js->old_length = 0;
 	js->keepalive_timeout = 0;
+	js->max_inactivity = DEFAULT_INACTIVITY_TIME;
 	/* Set the default protocol version to 1.0. Overridden in parser.c. */
 	js->protocol_version.major = 1;
 	js->protocol_version.minor = 0;
@@ -927,7 +1020,7 @@
 	js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain);
 
 	/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
-	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
+	if (g_str_equal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
 		if(purple_ssl_is_supported()) {
 			js->gsc = purple_ssl_connect(account, js->certificate_CN,
 					purple_account_get_int(account, "port", 5223),
@@ -1532,7 +1625,8 @@
 	g_free(js->avatar_hash);
 	g_free(js->caps_hash);
 
-	purple_circ_buffer_destroy(js->write_buffer);
+	if (js->write_buffer)
+		purple_circ_buffer_destroy(js->write_buffer);
 	if(js->writeh)
 		purple_input_remove(js->writeh);
 	if (js->auth_mech && js->auth_mech->dispose)
@@ -1543,6 +1637,8 @@
 	if(js->sasl_mechs)
 		g_string_free(js->sasl_mechs, TRUE);
 	g_free(js->sasl_cb);
+	/* Note: _not_ g_free.  See auth_cyrus.c:jabber_sasl_cb_secret */
+	free(js->sasl_secret);
 #endif
 	g_free(js->serverFQDN);
 	while(js->commands) {
@@ -1564,13 +1660,14 @@
 	g_free(js->old_source);
 	g_free(js->old_uri);
 	g_free(js->old_track);
-	g_free(js->expected_rspauth);
 
 	if (js->vcard_timer != 0)
 		purple_timeout_remove(js->vcard_timer);
 
 	if (js->keepalive_timeout != 0)
 		purple_timeout_remove(js->keepalive_timeout);
+	if (js->inactivity_timer != 0)
+		purple_timeout_remove(js->inactivity_timer);
 
 	g_free(js->srv_rec);
 	js->srv_rec = NULL;
@@ -1622,6 +1719,9 @@
 		case JABBER_STREAM_CONNECTED:
 			/* Send initial presence */
 			jabber_presence_send(js, TRUE);
+			/* Start up the inactivity timer */
+			jabber_stream_restart_inactivity_timer(js);
+
 			purple_connection_set_state(js->gc, PURPLE_CONNECTED);
 			break;
 	}
@@ -1867,20 +1967,53 @@
 	}
 }
 
-void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) {
+gint
+jabber_identity_compare(gconstpointer a, gconstpointer b)
+{
+	const JabberIdentity *ac;
+	const JabberIdentity *bc;
+	gint cat_cmp;
+	gint typ_cmp;
+
+	ac = a;
+	bc = b;
+
+	if ((cat_cmp = strcmp(ac->category, bc->category)) == 0) {
+		if ((typ_cmp = strcmp(ac->type, bc->type)) == 0) {
+			if (!ac->lang && !bc->lang) {
+				return 0;
+			} else if (ac->lang && !bc->lang) {
+				return 1;
+			} else if (!ac->lang && bc->lang) {
+				return -1;
+			} else {
+				return strcmp(ac->lang, bc->lang);
+			}
+		} else {
+			return typ_cmp;
+		}
+	} else {
+		return cat_cmp;
+	}
+}
+
+void jabber_add_identity(const gchar *category, const gchar *type,
+                         const gchar *lang, const gchar *name)
+{
 	GList *identity;
 	JabberIdentity *ident;
+
 	/* both required according to XEP-0030 */
 	g_return_if_fail(category != NULL);
 	g_return_if_fail(type != NULL);
 
-	for(identity = jabber_identities; identity; identity = identity->next) {
-		JabberIdentity *ident = (JabberIdentity*)identity->data;
-		if (!strcmp(ident->category, category) &&
-		    !strcmp(ident->type, type) &&
-		    ((!ident->lang && !lang) || (ident->lang && lang && !strcmp(ident->lang, lang)))) {
+	/* Check if this identity is already there... */
+	for (identity = jabber_identities; identity; identity = identity->next) {
+		JabberIdentity *id = identity->data;
+		if (g_str_equal(id->category, category) &&
+			g_str_equal(id->type, type) &&
+			purple_strequal(id->lang, lang))
 			return;
-		}
 	}
 
 	ident = g_new0(JabberIdentity, 1);
@@ -1888,7 +2021,8 @@
 	ident->type = g_strdup(type);
 	ident->lang = g_strdup(lang);
 	ident->name = g_strdup(name);
-	jabber_identities = g_list_prepend(jabber_identities, ident);
+	jabber_identities = g_list_insert_sorted(jabber_identities, ident,
+	                                         jabber_identity_compare);
 }
 
 static void jabber_identities_destroy(void)
@@ -1910,6 +2044,38 @@
 	       (!js->bosh && js->gsc);
 }
 
+static gboolean
+inactivity_cb(gpointer data)
+{
+	JabberStream *js = data;
+
+	/* We want whatever is sent to set this.  It's okay because
+	 * the eventloop unsets it via the return FALSE.
+	 */
+	js->inactivity_timer = 0;
+
+	if (js->bosh)
+		jabber_bosh_connection_send_keepalive(js->bosh);
+	else
+		jabber_send_raw(js, "\t", 1);
+
+	return FALSE;
+}
+
+void jabber_stream_restart_inactivity_timer(JabberStream *js)
+{
+	if (js->inactivity_timer != 0) {
+		purple_timeout_remove(js->inactivity_timer);
+		js->inactivity_timer = 0;
+	}
+
+	g_return_if_fail(js->max_inactivity > 0);
+
+	js->inactivity_timer =
+		purple_timeout_add_seconds(js->max_inactivity,
+		                           inactivity_cb, js);
+}
+
 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
 	return "jabber";
@@ -3313,8 +3479,7 @@
 			for (iter = jb->resources; iter ; iter = g_list_next(iter)) {
 				JabberBuddyResource *jbr = (JabberBuddyResource *) iter->data;
 
-				if (jabber_resource_has_capability(jbr,
-						"http://jabber.org/protocol/si/profile/file-transfer")
+				if (jabber_resource_has_capability(jbr, NS_SI_FILE_TRANSFER)
 			    	&& (jabber_resource_has_capability(jbr,
 			    			NS_BYTESTREAMS)
 			        	|| jabber_resource_has_capability(jbr, NS_IBB))) {
@@ -3336,7 +3501,7 @@
 
 	if (js->pep) {
 		/* if no argument was given, unset mood */
-		if (!args | !args[0]) {
+		if (!args || !args[0]) {
 			jabber_mood_set(js, NULL, NULL);
 		} else if (!args[1]) {
 			jabber_mood_set(js, args[0], NULL);
@@ -3616,7 +3781,7 @@
 	jabber_add_feature("http://jabber.org/protocol/muc", 0);
 	jabber_add_feature("http://jabber.org/protocol/muc#user", 0);
 	jabber_add_feature("http://jabber.org/protocol/si", 0);
-	jabber_add_feature("http://jabber.org/protocol/si/profile/file-transfer", 0);
+	jabber_add_feature(NS_SI_FILE_TRANSFER, 0);
 	jabber_add_feature(NS_XHTML_IM, 0);
 	jabber_add_feature(NS_PING, 0);
 
@@ -3675,6 +3840,11 @@
 	jabber_presence_uninit();
 	jabber_iq_uninit();
 
+#ifdef USE_VV
+	g_signal_handlers_disconnect_by_func(G_OBJECT(purple_media_manager_get()),
+			G_CALLBACK(jabber_caps_broadcast_change), NULL);
+#endif
+
 	jabber_auth_uninit();
 	jabber_features_destroy();
 	jabber_identities_destroy();