/pidgin/main: bbd52f93184e: Implement SASL support for IRC, usin...

Thijs Alkemade thijsalkemade at gmail.com
Tue Sep 25 20:30:00 EDT 2012


Changeset: bbd52f93184ef305cfb8e5400c3941ad40ca89c4
Author:	 Thijs Alkemade <thijsalkemade at gmail.com>
Date:	 2012-09-25 22:58 +0200
Branch:	 release-2.x.y
URL: http://hg.pidgin.im/pidgin/main/rev/bbd52f93184e

Description:

Implement SASL support for IRC, using Cyrus.

This is compatible with Freenode etc. That means
only PLAIN currently works, as Freenode only
offers PLAIN and DH-BLOWFISH, but we try a number
of others (as we can't query what the server
supports...) in case a server adds them.

Credit goes to Andy Spencer for the original patch.

Fixes #13270

diffstat:

 libpurple/protocols/irc/irc.c   |   32 +++-
 libpurple/protocols/irc/irc.h   |   18 +
 libpurple/protocols/irc/msgs.c  |  383 ++++++++++++++++++++++++++++++++++++++++
 libpurple/protocols/irc/parse.c |   13 +
 4 files changed, 445 insertions(+), 1 deletions(-)

diffs (truncated from 544 to 300 lines):

diff --git a/libpurple/protocols/irc/irc.c b/libpurple/protocols/irc/irc.c
--- a/libpurple/protocols/irc/irc.c
+++ b/libpurple/protocols/irc/irc.c
@@ -155,6 +155,7 @@ int irc_send_len(struct irc_conn *irc, c
  	char *tosend= g_strdup(buf);
 
 	purple_signal_emit(_irc_plugin, "irc-sending-text", purple_account_get_connection(irc->account), &tosend);
+	
 	if (tosend == NULL)
 		return 0;
 
@@ -392,9 +393,17 @@ static gboolean do_login(PurpleConnectio
 	const char *username, *realname;
 	struct irc_conn *irc = gc->proto_data;
 	const char *pass = purple_connection_get_password(gc);
+#ifdef HAVE_CYRUS_SASL
+	const gboolean use_sasl = purple_account_get_bool(irc->account, "sasl", FALSE);
+#endif
 
 	if (pass && *pass) {
-		buf = irc_format(irc, "v:", "PASS", pass);
+#ifdef HAVE_CYRUS_SASL
+		if (use_sasl)
+			buf = irc_format(irc, "vv:", "CAP", "REQ", "sasl");
+		else /* intended to fall through */
+#endif
+			buf = irc_format(irc, "v:", "PASS", pass);
 		if (irc_send(irc, buf) < 0) {
 			g_free(buf);
 			return FALSE;
@@ -526,6 +535,17 @@ static void irc_close(PurpleConnection *
 	g_free(irc->mode_chars);
 	g_free(irc->reqnick);
 
+#ifdef HAVE_CYRUS_SASL
+	if (irc->sasl_conn) {
+		sasl_dispose(&irc->sasl_conn);
+		irc->sasl_conn = NULL;
+	}
+	g_free(irc->sasl_cb);
+	if(irc->sasl_mechs)
+		g_string_free(irc->sasl_mechs, TRUE);
+#endif
+
+
 	g_free(irc);
 }
 
@@ -1045,6 +1065,16 @@ static void _init_plugin(PurplePlugin *p
 	option = purple_account_option_bool_new(_("Use SSL"), "ssl", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+#ifdef HAVE_CYRUS_SASL
+	option = purple_account_option_bool_new(_("Authenticate with SASL"), "sasl", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(
+						_("Allow plaintext SASL auth over unencrypted connection"),
+						"auth_plain_in_clear", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+#endif
+
 	_irc_plugin = plugin;
 
 	purple_prefs_remove("/plugins/prpl/irc/quitmsg");
diff --git a/libpurple/protocols/irc/irc.h b/libpurple/protocols/irc/irc.h
--- a/libpurple/protocols/irc/irc.h
+++ b/libpurple/protocols/irc/irc.h
@@ -25,6 +25,10 @@
 
 #include <glib.h>
 
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
 #include "circbuffer.h"
 #include "ft.h"
 #include "roomlist.h"
@@ -92,6 +96,13 @@ struct irc_conn {
 	char *mode_chars;
 	char *reqnick;
 	gboolean nickused;
+#ifdef HAVE_CYRUS_SASL
+	sasl_conn_t *sasl_conn;
+	const char *current_mech;
+	GString *sasl_mechs;
+	gboolean mech_works;
+	sasl_callback_t *sasl_cb;
+#endif
 };
 
 struct irc_buddy {
@@ -167,6 +178,13 @@ void irc_msg_unknown(struct irc_conn *ir
 void irc_msg_wallops(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_whois(struct irc_conn *irc, const char *name, const char *from, char **args);
 void irc_msg_who(struct irc_conn *irc, const char *name, const char *from, char **args);
+#ifdef HAVE_CYRUS_SASL
+void irc_msg_cap(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_auth(struct irc_conn *irc, char *arg);
+void irc_msg_authok(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_authtryagain(struct irc_conn *irc, const char *name, const char *from, char **args);
+void irc_msg_authfail(struct irc_conn *irc, const char *name, const char *from, char **args);
+#endif
 
 void irc_msg_ignore(struct irc_conn *irc, const char *name, const char *from, char **args);
 
diff --git a/libpurple/protocols/irc/msgs.c b/libpurple/protocols/irc/msgs.c
--- a/libpurple/protocols/irc/msgs.c
+++ b/libpurple/protocols/irc/msgs.c
@@ -32,6 +32,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
 static char *irc_mask_nick(const char *mask);
 static char *irc_mask_userhost(const char *mask);
 static void irc_chat_remove_buddy(PurpleConversation *convo, char *data[2]);
@@ -42,6 +46,10 @@ static void irc_msg_handle_privmsg(struc
                                    const char *from, const char *to,
                                    const char *rawmsg, gboolean notice);
 
+#ifdef HAVE_CYRUS_SASL
+static void irc_sasl_finish(struct irc_conn *irc);
+#endif
+
 static char *irc_mask_nick(const char *mask)
 {
 	char *end, *buf;
@@ -1426,6 +1434,381 @@ void irc_msg_wallops(struct irc_conn *ir
 	g_free(msg);
 }
 
+#ifdef HAVE_CYRUS_SASL
+static int
+irc_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
+{
+	struct irc_conn *irc = ctx;
+	const char *pw;
+	size_t len;
+
+	pw = purple_account_get_password(irc->account);
+
+	if (!conn || !secret || id != SASL_CB_PASS)
+		return SASL_BADPARAM;
+
+	len = strlen(pw);
+	/* Not an off-by-one because sasl_secret_t defines char data[1] */
+	/* TODO: This can probably be moved to glib's allocator */
+	sasl_secret_t *sasl_secret = malloc(sizeof(sasl_secret_t) + len);
+	if (!sasl_secret)
+		return SASL_NOMEM;
+
+	sasl_secret->len = len;
+	strcpy((char*)sasl_secret->data, pw);
+
+	*secret = sasl_secret;
+	return SASL_OK;
+}
+
+static int
+irc_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 int
+irc_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
+{
+	struct irc_conn *irc = ctx;
+	PurpleConnection *gc = purple_account_get_connection(irc->account);
+
+	switch(id) {
+		case SASL_CB_AUTHNAME:
+			*res = purple_connection_get_display_name(gc);
+			break;
+		case SASL_CB_USER:
+			*res = "";
+			break;
+		default:
+			return SASL_BADPARAM;
+	}
+	if (len) *len = strlen((char *)*res);
+	return SASL_OK;
+}
+
+static void
+irc_auth_start_cyrus(struct irc_conn *irc)
+{
+	int ret = 0;
+	char *buf;
+	sasl_security_properties_t secprops;
+	PurpleAccount *account = irc->account;
+	PurpleConnection *gc = purple_account_get_connection(account);
+
+	gboolean plaintext;
+	gboolean again = FALSE;
+
+	/* Set up security properties and options */
+	secprops.min_ssf = 0;
+	secprops.security_flags = SASL_SEC_NOANONYMOUS;
+
+	if (!irc->gsc) {
+		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 {
+		gchar *tmp = NULL;
+		again = FALSE;
+
+		ret = sasl_client_new("irc", irc->server, NULL, NULL, irc->sasl_cb, 0, &irc->sasl_conn);
+
+		if (ret != SASL_OK) {
+			purple_debug_error("irc", "sasl_client_new failed: %d\n", ret);
+			tmp = g_strdup_printf(_("Failed to initialize SASL authentication: %s"),
+				sasl_errdetail(irc->sasl_conn));
+			purple_connection_error_reason (gc,
+				PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+			g_free(tmp);
+			return;
+		}
+
+		sasl_setprop(irc->sasl_conn, SASL_AUTH_EXTERNAL, irc->account->username);
+		sasl_setprop(irc->sasl_conn, SASL_SEC_PROPS, &secprops);
+
+		ret = sasl_client_start(irc->sasl_conn, irc->sasl_mechs->str, NULL, NULL, NULL, &irc->current_mech);
+
+		switch (ret) {
+			case SASL_OK:
+			case SASL_CONTINUE:
+				irc->mech_works = FALSE;
+				break;
+			case SASL_NOMECH:
+				purple_connection_error_reason (gc,
+					PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
+					_("SASL authentication failed: No worthy authentication mechanisms found."));
+
+				irc_sasl_finish(irc);
+				return;
+			case SASL_BADPARAM:
+			case SASL_NOMEM:
+				tmp = g_strdup_printf(_("SASL authentication failed: %s"), sasl_errdetail(irc->sasl_conn));
+				purple_connection_error_reason (gc,
+					PURPLE_CONNECTION_ERROR_OTHER_ERROR, tmp);
+				g_free(tmp);
+
+				irc_sasl_finish(irc);
+				return;
+			default:
+				purple_debug_error("irc", "sasl_client_start failed: %s\n", sasl_errdetail(irc->sasl_conn));
+
+				if (irc->current_mech && *irc->current_mech) {
+					char *pos;
+					if ((pos = strstr(irc->sasl_mechs->str, irc->current_mech))) {
+						size_t index = pos - irc->sasl_mechs->str;
+						g_string_erase(irc->sasl_mechs, index, strlen(irc->current_mech));
+
+						/* Remove space which separated this mech from the next */
+						if ((irc->sasl_mechs->str)[index] == ' ') {
+							g_string_erase(irc->sasl_mechs, index, 1);
+						}
+					}
+
+					again = TRUE;
+				}
+				irc_sasl_finish(irc);
+		}
+	} while (again);
+
+	purple_debug_info("irc", "Using SASL: %s\n", irc->current_mech);
+
+	buf = irc_format(irc, "vv", "AUTHENTICATE", irc->current_mech);
+	irc_send(irc, buf);
+	g_free(buf);
+}
+
+/* SASL authentication */
+void
+irc_msg_cap(struct irc_conn *irc, const char *name, const char *from, char **args)
+{
+	int ret = 0;
+	int id = 0;
+	PurpleConnection *gc = purple_account_get_connection(irc->account);



More information about the Commits mailing list