aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconfigure2
-rw-r--r--protocols/jabber/jabber.c4
-rw-r--r--protocols/jabber/jabber.h1
-rw-r--r--protocols/jabber/sasl.c105
4 files changed, 89 insertions, 23 deletions
diff --git a/configure b/configure
index cabefe7c..087b2bc4 100755
--- a/configure
+++ b/configure
@@ -876,6 +876,8 @@ if [ "$protocols" = "PROTOCOLS = " ]; then
echo " BitlBee will run, but you will be unable to connect to IM servers!"
fi
+echo "EFLAGS+=$(pkg-config libidn --libs)" >> Makefile.settings
+
echo "PROTOCOLS = $protocols" >> Makefile.settings
echo "PROTOOBJS = $protoobjs" >> Makefile.settings
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index 2fa19c0f..fcd90598 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -116,6 +116,9 @@ static void jabber_init(account_t *acc)
s = set_add(&acc->set, "carbons", "true", set_eval_bool, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
+ s = set_add(&acc->set, "disable_scram", "false", set_eval_bool, acc);
+ s->flags |= SET_HIDDEN_DEFAULT;
+
acc->flags |= ACC_FLAG_AWAY_MESSAGE | ACC_FLAG_STATUS_MESSAGE |
ACC_FLAG_HANDLE_DOMAINS;
}
@@ -379,6 +382,7 @@ static void jabber_logout(struct im_connection *ic)
g_free(jd->me);
g_free(jd->challenge.cnonce);
g_free(jd->challenge.server_signature);
+ g_free(jd->challenge.cb_header);
g_free(jd);
jabber_connections = g_slist_remove(jabber_connections, ic);
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index 3445e4d7..c97c5b71 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -109,6 +109,7 @@ struct jabber_data {
int scram_algo;
char *cnonce;
char *server_signature;
+ char *cb_header;
} challenge;
const struct oauth2_service *oauth2_service;
diff --git a/protocols/jabber/sasl.c b/protocols/jabber/sasl.c
index 10fbe270..e3467d86 100644
--- a/protocols/jabber/sasl.c
+++ b/protocols/jabber/sasl.c
@@ -23,6 +23,8 @@
#include <ctype.h>
#include <gcrypt.h>
+#include <gnutls/gnutls.h>
+#include <stringprep.h>
#include "jabber.h"
#include "base64.h"
@@ -40,7 +42,7 @@ const struct oauth2_service oauth2_service_google =
"6C-Zgf7Tr7gEQTPlBhMUgo7R",
};
-static int is_ascii(const char *str)
+/*static int is_ascii(const char *str)
{
if (!str) {
return 0;
@@ -52,7 +54,7 @@ static int is_ascii(const char *str)
}
return 1;
-}
+}*/
xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
{
@@ -60,7 +62,7 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
struct jabber_data *jd = ic->proto_data;
struct xt_node *c, *reply;
char *s;
- int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0;
+ int sup_plain = 0, sup_digest = 0, sup_gtalk = 0, sup_anonymous = 0, sup_scram = 0, sup_scram_cb = 0;
int want_oauth = FALSE, want_hipchat = FALSE, want_anonymous = FALSE;
GString *mechs;
@@ -97,9 +99,15 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
} else if (g_strcasecmp(c->text, "X-OAUTH2") == 0) {
sup_gtalk = 1;
} else if (g_strcasecmp(c->text, "SCRAM-SHA-1") == 0) {
- sup_scram = JSCRAM_SHA1;
+ sup_scram |= JSCRAM_SHA1;
+ } else if (g_strcasecmp(c->text, "SCRAM-SHA-1-PLUS") == 0) {
+ sup_scram |= JSCRAM_SHA1;
+ sup_scram_cb |= JSCRAM_SHA1;
} else if (g_strcasecmp(c->text, "SCRAM-SHA-256") == 0) {
- sup_scram = JSCRAM_SHA256;
+ sup_scram |= JSCRAM_SHA256;
+ } else if (g_strcasecmp(c->text, "SCRAM-SHA-256-PLUS") == 0) {
+ sup_scram |= JSCRAM_SHA256;
+ sup_scram_cb |= JSCRAM_SHA256;
}
g_string_append_printf(mechs, " %s", c->text);
@@ -108,9 +116,14 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
c = c->next;
}
- if (sup_scram && (!is_ascii(jd->username) || !is_ascii(ic->acc->pass))) {
+ /*if (sup_scram && (!is_ascii(jd->username) || !is_ascii(ic->acc->pass))) {
imcb_log(ic, "Username/password contains non-ascii characters, SCRAM authentication disabled");
sup_scram = 0;
+ }*/
+
+ if (sup_scram_cb && !jd->ssl) {
+ imcb_log(ic, "Not connected over TLS, SCRAM Channel bindings disabled");
+ sup_scram_cb = 0;
}
if (!want_oauth && !want_anonymous && !sup_plain && !sup_digest && !sup_scram) {
@@ -164,20 +177,28 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
imc_logout(ic, FALSE);
xt_free_node(reply);
return XT_ABORT;
- } else if (sup_scram) {
+ } else if (sup_scram && !set_getbool(&ic->acc->set, "disable_scram")) {
GString *gs;
- int len;
+ int len, r;
unsigned char cnonce_bin[30];
- char *cnonce;
+ char *cnonce, *puser = NULL;
- if (sup_scram & JSCRAM_SHA256) {
+ if (sup_scram & JSCRAM_SHA256 && (!sup_scram_cb || (sup_scram_cb & JSCRAM_SHA256))) {
jd->challenge.type = JCHALLENGE_SCRAM;
jd->challenge.scram_algo = GCRY_MD_SHA256;
- xt_add_attr(reply, "mechanism", "SCRAM-SHA-256");
- } else if (sup_scram & JSCRAM_SHA1) {
+ if (sup_scram_cb) {
+ xt_add_attr(reply, "mechanism", "SCRAM-SHA-256-PLUS");
+ } else {
+ xt_add_attr(reply, "mechanism", "SCRAM-SHA-256");
+ }
+ } else if (sup_scram & JSCRAM_SHA1 && (!sup_scram_cb || (sup_scram_cb & JSCRAM_SHA1))) {
jd->challenge.type = JCHALLENGE_SCRAM;
jd->challenge.scram_algo = GCRY_MD_SHA1;
- xt_add_attr(reply, "mechanism", "SCRAM-SHA-1");
+ if (sup_scram_cb) {
+ xt_add_attr(reply, "mechanism", "SCRAM-SHA-1-PLUS");
+ } else {
+ xt_add_attr(reply, "mechanism", "SCRAM-SHA-1");
+ }
} else {
imcb_error(ic, "Unknown scram method"); /* Just in case, but we should not get here */
return XT_ABORT;
@@ -190,14 +211,22 @@ xt_status sasl_pkt_mechanisms(struct xt_node *node, gpointer data)
jd->challenge.cnonce = cnonce;
- /* XXX: Saslprep username */
- g_string_append_printf(gs, "n,,n=%s,r=%s", jd->username, cnonce);
+ r = stringprep_profile(jd->username, &puser, "SASLprep", 0);
+ if (r != STRINGPREP_OK) {
+ imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r));
+ return XT_ABORT;
+ }
+
+ //jd->challenge.cb_header = g_strdup_printf("%s,,", sup_scram_cb ? "y" : "p=tls-unique");
+ jd->challenge.cb_header = g_strdup_printf("%s,,", "n");
+ g_string_append_printf(gs, "%sn=%s,r=%s", jd->challenge.cb_header, puser, cnonce);
len = gs->len;
s = g_string_free(gs, FALSE);
reply->text = base64_encode((unsigned char *)s , len);
reply->text_len = strlen(reply->text);
+ g_free(puser);
g_free(s);
} else if (sup_digest && !(jd->ssl && sup_plain)) {
/* Only try DIGEST-MD5 if there's no SSL/TLS or if PLAIN isn't supported.
@@ -468,12 +497,13 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data)
struct xt_node *reply_pkt = NULL;
xt_status ret = XT_ABORT;
char *random = NULL, *salt64 = NULL, *iter_str = NULL, *encoded_proof = NULL,
- *reply = NULL, *client_first_bare = NULL, *server_first = NULL,
- *client_final_noproof = NULL, *auth_message = NULL, *client_final = NULL;
+ *reply = NULL, *client_first_bare = NULL, *server_first = NULL, *cb_header64 = NULL,
+ *client_final_noproof = NULL, *auth_message = NULL, *client_final = NULL, *puser = NULL, *ppass = NULL;
+
unsigned char *salt = NULL;
size_t salt_len, iter_count, i;
- int algo = jd->challenge.scram_algo;
+ int algo = jd->challenge.scram_algo, r;
size_t md_len = gcry_md_get_algo_dlen(algo);
unsigned char client_key[md_len];
@@ -500,15 +530,24 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data)
goto error;
}
- // XXX: Normalize password with saslprep
+ r = stringprep_profile(jd->username, &puser, "SASLprep", 0);
+ if (r != STRINGPREP_OK) {
+ imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r));
+ goto error;
+ }
+ r = stringprep_profile(ic->acc->pass, &ppass, "SASLprep", 0);
+ if (r != STRINGPREP_OK) {
+ imcb_error(ic, "SASLprep failed: %s", stringprep_strerror(r));
+ goto error;
+ }
salt_len = base64_decode(salt64, &salt);
- if (gcry_kdf_derive(ic->acc->pass, strlen(ic->acc->pass),
+ if (gcry_kdf_derive(ppass, strlen(ppass),
GCRY_KDF_PBKDF2, algo,
salt, salt_len, iter_count,
sizeof(salted_password), salted_password) != 0) {
- imcb_error(ic, "gcrypt went booboo :(");
+ imcb_error(ic, "PBKDF failed");
goto error;
}
@@ -516,8 +555,23 @@ static xt_status sasl_pkt_challenge_scram(struct xt_node *node, gpointer data)
gcry_md_hash_buffer(algo, stored_key, client_key, sizeof(client_key));
- client_first_bare = g_strdup_printf("n=%s,r=%s", jd->username, jd->challenge.cnonce);
- client_final_noproof = g_strdup_printf("c=biws,r=%s", random);
+ /*if (jd->challenge.cb_header[0] == 'p') {
+ gnutls_datum_t cb;
+ int rc;
+
+ rc = gnutls_session_channel_binding(jd->ssl, GNUTLS_CB_TLS_UNIQUE, &cb);
+ if (rc) {
+ imcb_log(ic, "Chanel binding error: %s", gnutls_strerror(rc));
+ return XT_ABORT;
+ }
+
+ for (size_t i = 0; i < cb.size; i++)
+ fprintf(stderr, "%02x", cb.data[i]);
+ fprintf(stderr, "\n");
+ }*/
+ cb_header64 = tobase64(jd->challenge.cb_header);
+ client_first_bare = g_strdup_printf("n=%s,r=%s", puser, jd->challenge.cnonce);
+ client_final_noproof = g_strdup_printf("c=%s,r=%s", cb_header64, random);
auth_message = g_strdup_printf("%s,%s,%s", client_first_bare, server_first, client_final_noproof);
@@ -551,11 +605,14 @@ error:
imc_logout(ic, FALSE);
silent_error:
+ g_free(puser);
+ g_free(ppass);
g_free(random);
g_free(salt64);
g_free(salt);
g_free(iter_str);
g_free(encoded_proof);
+ g_free(cb_header64);
g_free(client_first_bare);
g_free(client_final_noproof);
g_free(client_final);
@@ -563,6 +620,8 @@ silent_error:
g_free(reply);
g_free(jd->challenge.cnonce);
jd->challenge.cnonce = NULL;
+ g_free(jd->challenge.cb_header);
+ jd->challenge.cb_header = NULL;
xt_free_node(reply_pkt);
return ret;
}