pidgin/pidgin

[gaim-migrate @ 14820]

2005-12-17, Simon Wilkinson
32f6f8bf3a57
Parents 9c4e2db872fd
Children 6ecf4cc35f4c
[gaim-migrate @ 14820]
patch from Simon Wilkinson to add Cyrus SASL support for jabber

Give him credit if it works flawlessly. Blame me if it doesn't, as the
patch was against 1.3.1 (yeah, I've been sitting on it for that long), and
I had to merge it to HEAD, and clean up a bunch of warnings

committer: Nathan Walp
--- a/COPYRIGHT Fri Dec 16 21:41:46 2005 +0000
+++ b/COPYRIGHT Sat Dec 17 02:24:05 2005 +0000
@@ -264,6 +264,7 @@
Dave West
Daniel Westermann-Clark
Andrew Whewell
+Simon Wilkinson
Dan Willemsen
Jason Willis
Matt Wilson
--- a/configure.ac Fri Dec 16 21:41:46 2005 +0000
+++ b/configure.ac Sat Dec 17 02:24:05 2005 +0000
@@ -1402,6 +1402,11 @@
dnl checks for jabber
dnl AC_CHECK_SIZEOF(short)
AC_CHECK_FUNCS(snprintf connect)
+AC_SUBST(SASL_LIBS)
+AC_ARG_ENABLE(cyrus-sasl, AC_HELP_STRING([--enable-cyrus-sasl], [enable Cyrus SASL support for jabberd (no)]), enable_cyrus_sasl=$enableval, enable_cyrus_sasl=no)
+if test "x-$enable_cyrus_sasl" = "x-yes" ; then
+ AC_CHECK_LIB(sasl2, sasl_client_init, [AC_DEFINE(HAVE_CYRUS_SASL, [1], [Define to 1 if Cyrus SASL is present]) SASL_LIBS=-"lsasl2"], [AC_ERROR(Cyrus SASL library not found)])
+fi
dnl checks for zephyr
AC_DEFINE(ZEPHYR_INT32, long, [Size of an int32.])
@@ -1676,11 +1681,13 @@
echo Build with GtkSpell support... : $enable_gtkspell
echo Build with Voice/Video support : $enable_vv
echo Build with DBUS support....... : $enable_dbus
+echo Build with Cyrus SASL support. : $enable_cyrus_sasl
if test x$enable_dbus = xyes ; then
echo DBUS session directory........ : $DBUS_SESSION_DIR
fi
echo Has you....................... : yes
echo
+echo
echo Use kerberos 4 with zephyr.... : $kerberos
echo Use external libzephyr........ : $zephyr
echo
--- a/src/protocols/jabber/Makefile.am Fri Dec 16 21:41:46 2005 +0000
+++ b/src/protocols/jabber/Makefile.am Sat Dec 17 02:24:05 2005 +0000
@@ -36,7 +36,7 @@
AM_CFLAGS = $(st)
-libjabber_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS)
+libjabber_la_LDFLAGS = -module -avoid-version $(GLIB_LIBS) $(SASL_LIBS)
if STATIC_JABBER
--- a/src/protocols/jabber/auth.c Fri Dec 16 21:41:46 2005 +0000
+++ b/src/protocols/jabber/auth.c Sat Dec 17 02:24:05 2005 +0000
@@ -114,12 +114,188 @@
gaim_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream"));
}
+#ifdef HAVE_CYRUS_SASL
+
+static void jabber_auth_start_cyrus(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)
+{
+ JabberStream *js = (JabberStream *)ctx;
+
+ switch(id) {
+ case SASL_CB_AUTHNAME:
+ *res = js->user->node;
+ break;
+ case SASL_CB_USER:
+ *res = js->user->node;
+ 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 = gaim_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(GaimAccount *account)
+{
+ gaim_account_set_bool(account, "auth_plain_in_clear", TRUE);
+
+ jabber_auth_start_cyrus(account->gc->proto_data);
+}
+
+static void jabber_auth_start_cyrus(JabberStream *js)
+{
+ const char *clientout, *mech;
+ char *enc_out;
+ unsigned coutlen;
+ 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 (!js->gsc) {
+ plaintext = gaim_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
+ if (!plaintext)
+ secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
+ secprops.max_ssf = -1;
+ secprops.maxbufsize = 4096;
+ } else {
+ plaintext = FALSE;
+ secprops.max_ssf = 0;
+ secprops.maxbufsize = 0;
+ }
+ secprops.property_names = 0;
+ secprops.property_values = 0;
+
+ do {
+ again = FALSE;
+ /* Use the user's domain for compatibility with the old
+ * DIGESTMD5 code. Note that this may cause problems where
+ * the user's domain doesn't match the FQDN of the jabber
+ * service
+ */
+
+ js->sasl_state = sasl_client_new("xmpp", js->user->domain, NULL, NULL, js->sasl_cb, 0, &js->sasl);
+ if (js->sasl_state==SASL_OK) {
+ sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
+ js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &mech);
+ }
+ switch (js->sasl_state) {
+ /* Success */
+ case SASL_CONTINUE:
+ break;
+ case SASL_NOMECH:
+ /* No mechanisms do what we want. See if we can add
+ * plaintext ones to the list. */
+
+ if (!gaim_account_get_password(js->gc->account)) {
+ gaim_connection_error(js->gc, _("Server couldn't authenticate you without a password"));
+ return;
+ } else if (!plaintext) {
+ gaim_request_yes_no(js->gc, _("Plaintext Authentication"),
+ _("Plaintext Authentication"),
+ _("This server requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
+ 2, js->gc->account,
+ allow_cyrus_plaintext_auth,
+ disallow_plaintext_auth);
+ return;
+ } else {
+ gaim_connection_error(js->gc, _("Server does not use any supported authentication method"));
+ 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:
+ if (strlen(mech)>0) {
+ char *pos;
+ pos = strstr(js->sasl_mechs->str,mech);
+ g_assert(pos!=NULL);
+ g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str,strlen(mech));
+ }
+ sasl_dispose(&js->sasl);
+ again=TRUE;
+ }
+ } while (again);
+
+ if (js->sasl_state == SASL_CONTINUE) {
+ auth = xmlnode_new("auth");
+ xmlnode_set_attrib(auth, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
+ xmlnode_set_attrib(auth,"mechanism", mech);
+ if (clientout) {
+ if (coutlen == 0) {
+ xmlnode_insert_data(auth, "=", -1);
+ } else {
+ enc_out = gaim_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 {
+ gaim_connection_error(js->gc, "SASL authentication failed\n");
+ }
+}
+
+#endif
+
void
jabber_auth_start(JabberStream *js, xmlnode *packet)
{
- xmlnode *mechs, *mechnode;
+#ifdef HAVE_CYRUS_SASL
+ int id;
+#else
+ gboolean digest_md5 = FALSE, plain=FALSE;
+#endif
- gboolean digest_md5 = FALSE, plain=FALSE;
+ xmlnode *mechs, *mechnode;
if(js->registration) {
@@ -134,17 +310,59 @@
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
+ g_string_append(js->sasl_mechs, mech_name);
+ g_string_append_c(js->sasl_mechs,' ');
+#else
if(mech_name && !strcmp(mech_name, "DIGEST-MD5"))
digest_md5 = TRUE;
else if(mech_name && !strcmp(mech_name, "PLAIN"))
plain = TRUE;
+#endif
g_free(mech_name);
}
+#ifdef HAVE_CYRUS_SASL
+ js->auth_type = JABBER_AUTH_CYRUS;
+
+ /* Set up our callbacks structure */
+ js->sasl_cb = g_new0(sasl_callback_t,5);
+
+ 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 (gaim_account_get_password(js->gc->account)) {
+ 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_LIST_END;
+
+ jabber_auth_start_cyrus(js);
+#else
if(digest_md5) {
xmlnode *auth;
@@ -172,6 +390,7 @@
gaim_connection_error(js->gc,
_("Server does not use any supported authentication method"));
}
+#endif
}
static void auth_old_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
@@ -463,17 +682,70 @@
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,declen;
+ xmlnode *response;
+
+ dec_in = gaim_base64_decode(enc_in, &declen);
+
+ js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen,
+ NULL, &c_out, &clen);
+ g_free(dec_in);
+ if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) {
+ gaim_debug_error("jabber", "Error is %d : %s\n",js->sasl_state,sasl_errdetail(js->sasl));
+ gaim_connection_error(js->gc, _("SASL error"));
+ return;
+ } else {
+ response = xmlnode_new("response");
+ xmlnode_set_attrib(response, "xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
+ if (c_out) {
+ enc_out = gaim_base64_encode((unsigned char*)c_out, clen);
+ 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_attrib(packet, "xmlns");
+#ifdef HAVE_CYRUS_SASL
+ int *x;
+#endif
if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
gaim_connection_error(js->gc, _("Invalid response from server."));
return;
}
+#if 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) {
+ js->sasl_state = sasl_client_step(js->sasl, NULL, 0, NULL, NULL, NULL);
+ if (js->sasl_state != SASL_OK) {
+ /* This should never happen! */
+ gaim_connection_error(js->gc, _("Invalid response from server."));
+ }
+ }
+ /* If we've negotiated a security layer, we need to enable it */
+ sasl_getprop(js->sasl, SASL_SSF, (const void **)&x);
+ if (*x>0) {
+ sasl_getprop(js->sasl, SASL_MAXOUTBUF, (const void **)&x);
+ js->sasl_maxbuf = *x;
+ }
+#endif
+
jabber_stream_set_state(js, JABBER_STREAM_REINITIALIZING);
}
--- a/src/protocols/jabber/jabber.c Fri Dec 16 21:41:46 2005 +0000
+++ b/src/protocols/jabber/jabber.c Sat Dec 17 02:24:05 2005 +0000
@@ -203,6 +203,42 @@
gaim_debug(GAIM_DEBUG_MISC, "jabber", "Sending%s: %s\n",
js->gsc ? " (ssl)" : "", 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
+ if (js->sasl_maxbuf>0) {
+ int pos;
+
+ if (!js->gsc && js->fd<0)
+ return;
+ pos = 0;
+ if (len == -1)
+ len = strlen(data);
+ while (pos < len) {
+ int towrite;
+ const char *out;
+ unsigned olen;
+
+ if ((len - pos) < js->sasl_maxbuf)
+ towrite = len - pos;
+ else
+ towrite = js->sasl_maxbuf;
+
+ sasl_encode(js->sasl, &data[pos], towrite, &out, &olen);
+ pos += towrite;
+
+ if (js->gsc)
+ ret = gaim_ssl_write(js->gsc, out, olen);
+ else
+ ret = write(js->fd, out, olen);
+ if (ret < 0)
+ gaim_connection_error(js->gc, _("Write error"));
+ }
+ return;
+ }
+#endif
+
if(js->gsc) {
ret = gaim_ssl_write(js->gsc, data, len == -1 ? strlen(data) : len);
} else {
@@ -266,6 +302,18 @@
return;
if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+#ifdef HAVE_CYRUS_SASL
+ if (js->sasl_maxbuf>0) {
+ const char *out;
+ int olen;
+ sasl_decode(js->sasl, buf, len, &out, &olen);
+ if (olen>0) {
+ gaim_debug(GAIM_DEBUG_INFO, "jabber", "RecvSASL (%d): %s\n", olen, out);
+ jabber_parser_process(js,out,olen);
+ }
+ return;
+ }
+#endif
buf[len] = '\0';
gaim_debug(GAIM_DEBUG_INFO, "jabber", "Recv (%d): %s\n", len, buf);
jabber_parser_process(js, buf, len);
@@ -819,6 +867,14 @@
jabber_id_free(js->user);
if(js->avatar_hash)
g_free(js->avatar_hash);
+#ifdef HAVE_CYRUS_SASL
+ if(js->sasl)
+ sasl_dispose(&js->sasl);
+ if(js->sasl_mechs)
+ g_string_free(js->sasl_mechs, TRUE);
+ if(js->sasl_cb)
+ g_free(js->sasl_cb);
+#endif
g_free(js);
gc->proto_data = NULL;
@@ -1608,7 +1664,8 @@
static GaimPluginProtocolInfo prpl_info =
{
- OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME,
+ OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+ OPT_PROTO_PASSWORD_OPTIONAL,
NULL, /* user_splits */
NULL, /* protocol_options */
{"jpeg,gif,png", 0, 0, 96, 96, GAIM_ICON_SCALE_DISPLAY}, /* icon_spec */
@@ -1743,6 +1800,10 @@
gaim_prefs_remove("/plugins/prpl/jabber");
+ /* XXX - If any other plugin wants SASL this won't be good ... */
+#ifdef HAVE_CYRUS_SASL
+ sasl_client_init(NULL);
+#endif
jabber_register_commands();
}
--- a/src/protocols/jabber/jabber.h Fri Dec 16 21:41:46 2005 +0000
+++ b/src/protocols/jabber/jabber.h Sat Dec 17 02:24:05 2005 +0000
@@ -30,6 +30,10 @@
#include "jutil.h"
#include "xmlnode.h"
+#ifdef HAVE_CYRUS_SASL
+#include <sasl/sasl.h>
+#endif
+
typedef enum {
JABBER_CAP_NONE = 0,
JABBER_CAP_XHTML = 1 << 0,
@@ -68,7 +72,8 @@
JABBER_AUTH_UNKNOWN,
JABBER_AUTH_DIGEST_MD5,
JABBER_AUTH_PLAIN,
- JABBER_AUTH_IQ_AUTH
+ JABBER_AUTH_IQ_AUTH,
+ JABBER_AUTH_CYRUS
} auth_type;
char *stream_id;
JabberStreamState state;
@@ -102,6 +107,18 @@
char *avatar_hash;
GSList *pending_avatar_requests;
+
+ /* OK, this stays at the end of the struct, so plugins can depend
+ * on the rest of the stuff being in the right place
+ */
+#ifdef HAVE_CYRUS_SASL
+ sasl_conn_t *sasl;
+ sasl_callback_t *sasl_cb;
+ int sasl_state;
+ int sasl_maxbuf;
+ GString *sasl_mechs;
+#endif
+
} JabberStream;
void jabber_process_packet(JabberStream *js, xmlnode *packet);