comparison libpurple/protocols/jabber/auth.c @ 29190:4491a662d527

merged with im.pidgin.pidgin
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Thu, 17 Dec 2009 16:50:12 +0900
parents b94fd073187c
children c64b22932ffa
comparison
equal deleted inserted replaced
29070:5c77b620375c 29190:4491a662d527
37 #include "jabber.h" 37 #include "jabber.h"
38 #include "jutil.h" 38 #include "jutil.h"
39 #include "iq.h" 39 #include "iq.h"
40 #include "notify.h" 40 #include "notify.h"
41 41
42 static GSList *auth_mechs = NULL;
43
42 static void auth_old_result_cb(JabberStream *js, const char *from, 44 static void auth_old_result_cb(JabberStream *js, const char *from,
43 JabberIqType type, const char *id, 45 JabberIqType type, const char *id,
44 xmlnode *packet, gpointer data); 46 xmlnode *packet, gpointer data);
45 47
46 gboolean 48 gboolean
47 jabber_process_starttls(JabberStream *js, xmlnode *packet) 49 jabber_process_starttls(JabberStream *js, xmlnode *packet)
48 { 50 {
51 PurpleAccount *account;
49 xmlnode *starttls; 52 xmlnode *starttls;
53
54 account = purple_connection_get_account(js->gc);
50 55
51 if((starttls = xmlnode_get_child(packet, "starttls"))) { 56 if((starttls = xmlnode_get_child(packet, "starttls"))) {
52 if(purple_ssl_is_supported()) { 57 if(purple_ssl_is_supported()) {
53 jabber_send_raw(js, 58 jabber_send_raw(js,
54 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1); 59 "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -1);
56 } else if(xmlnode_get_child(starttls, "required")) { 61 } else if(xmlnode_get_child(starttls, "required")) {
57 purple_connection_error_reason(js->gc, 62 purple_connection_error_reason(js->gc,
58 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, 63 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
59 _("Server requires TLS/SSL, but no TLS/SSL support was found.")); 64 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
60 return TRUE; 65 return TRUE;
61 } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { 66 } else if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
62 purple_connection_error_reason(js->gc, 67 purple_connection_error_reason(js->gc,
63 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, 68 PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
64 _("You require encryption, but no TLS/SSL support was found.")); 69 _("You require encryption, but no TLS/SSL support was found."));
65 return TRUE; 70 return TRUE;
66 } 71 }
69 return FALSE; 74 return FALSE;
70 } 75 }
71 76
72 static void finish_plaintext_authentication(JabberStream *js) 77 static void finish_plaintext_authentication(JabberStream *js)
73 { 78 {
74 if(js->auth_type == JABBER_AUTH_PLAIN) { 79 JabberIq *iq;
75 xmlnode *auth; 80 xmlnode *query, *x;
76 GString *response; 81
77 gchar *enc_out; 82 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
78 83 query = xmlnode_get_child(iq->node, "query");
79 auth = xmlnode_new("auth"); 84 x = xmlnode_new_child(query, "username");
80 xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl"); 85 xmlnode_insert_data(x, js->user->node, -1);
81 86 x = xmlnode_new_child(query, "resource");
82 xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); 87 xmlnode_insert_data(x, js->user->resource, -1);
83 xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); 88 x = xmlnode_new_child(query, "password");
84 89 xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
85 response = g_string_new(""); 90 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
86 response = g_string_append_len(response, "\0", 1); 91 jabber_iq_send(iq);
87 response = g_string_append(response, js->user->node);
88 response = g_string_append_len(response, "\0", 1);
89 response = g_string_append(response,
90 purple_connection_get_password(js->gc));
91
92 enc_out = purple_base64_encode((guchar *)response->str, response->len);
93
94 xmlnode_set_attrib(auth, "mechanism", "PLAIN");
95 xmlnode_insert_data(auth, enc_out, -1);
96 g_free(enc_out);
97 g_string_free(response, TRUE);
98
99 jabber_send(js, auth);
100 xmlnode_free(auth);
101 } else if(js->auth_type == JABBER_AUTH_IQ_AUTH) {
102 JabberIq *iq;
103 xmlnode *query, *x;
104
105 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
106 query = xmlnode_get_child(iq->node, "query");
107 x = xmlnode_new_child(query, "username");
108 xmlnode_insert_data(x, js->user->node, -1);
109 x = xmlnode_new_child(query, "resource");
110 xmlnode_insert_data(x, js->user->resource, -1);
111 x = xmlnode_new_child(query, "password");
112 xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
113 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
114 jabber_iq_send(iq);
115 }
116 } 92 }
117 93
118 static void allow_plaintext_auth(PurpleAccount *account) 94 static void allow_plaintext_auth(PurpleAccount *account)
119 { 95 {
96 PurpleConnection *gc;
97 JabberStream *js;
98
120 purple_account_set_bool(account, "auth_plain_in_clear", TRUE); 99 purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
121 100
122 finish_plaintext_authentication(account->gc->proto_data); 101 gc = purple_account_get_connection(account);
102 js = purple_connection_get_protocol_data(gc);
103
104 finish_plaintext_authentication(js);
123 } 105 }
124 106
125 static void disallow_plaintext_auth(PurpleAccount *account) 107 static void disallow_plaintext_auth(PurpleAccount *account)
126 { 108 {
127 purple_connection_error_reason(account->gc, 109 purple_connection_error_reason(purple_account_get_connection(account),
128 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, 110 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
129 _("Server requires plaintext authentication over an unencrypted stream")); 111 _("Server requires plaintext authentication over an unencrypted stream"));
130 } 112 }
131 113
132 #ifdef HAVE_CYRUS_SASL 114 #ifdef HAVE_CYRUS_SASL
133 115 static void
134 static void jabber_auth_start_cyrus(JabberStream *); 116 auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
135 static void jabber_sasl_build_callbacks(JabberStream *); 117 {
136 118 PurpleAccount *account;
137 /* Callbacks for Cyrus SASL */ 119 JabberStream *js;
138
139 static int jabber_sasl_cb_realm(void *ctx, int id, const char **avail, const char **result)
140 {
141 JabberStream *js = (JabberStream *)ctx;
142
143 if (id != SASL_CB_GETREALM || !result) return SASL_BADPARAM;
144
145 *result = js->user->domain;
146
147 return SASL_OK;
148 }
149
150 static int jabber_sasl_cb_simple(void *ctx, int id, const char **res, unsigned *len)
151 {
152 JabberStream *js = (JabberStream *)ctx;
153
154 switch(id) {
155 case SASL_CB_AUTHNAME:
156 *res = js->user->node;
157 break;
158 case SASL_CB_USER:
159 *res = "";
160 break;
161 default:
162 return SASL_BADPARAM;
163 }
164 if (len) *len = strlen((char *)*res);
165 return SASL_OK;
166 }
167
168 static int jabber_sasl_cb_secret(sasl_conn_t *conn, void *ctx, int id, sasl_secret_t **secret)
169 {
170 JabberStream *js = (JabberStream *)ctx;
171 const char *pw = purple_account_get_password(js->gc->account);
172 size_t len;
173 static sasl_secret_t *x = NULL;
174
175 if (!conn || !secret || id != SASL_CB_PASS)
176 return SASL_BADPARAM;
177
178 len = strlen(pw);
179 x = (sasl_secret_t *) realloc(x, sizeof(sasl_secret_t) + len);
180
181 if (!x)
182 return SASL_NOMEM;
183
184 x->len = len;
185 strcpy((char*)x->data, pw);
186
187 *secret = x;
188 return SASL_OK;
189 }
190
191 static void allow_cyrus_plaintext_auth(PurpleAccount *account)
192 {
193 purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
194
195 jabber_auth_start_cyrus(account->gc->proto_data);
196 }
197
198 static gboolean auth_pass_generic(JabberStream *js, PurpleRequestFields *fields)
199 {
200 const char *entry; 120 const char *entry;
201 gboolean remember; 121 gboolean remember;
202 122
123 /* The password prompt dialog doesn't get disposed if the account disconnects */
124 if (!PURPLE_CONNECTION_IS_VALID(gc))
125 return;
126
127 account = purple_connection_get_account(gc);
128 js = purple_connection_get_protocol_data(gc);
129
203 entry = purple_request_fields_get_string(fields, "password"); 130 entry = purple_request_fields_get_string(fields, "password");
204 remember = purple_request_fields_get_bool(fields, "remember"); 131 remember = purple_request_fields_get_bool(fields, "remember");
205 132
206 if (!entry || !*entry) 133 if (!entry || !*entry)
207 { 134 {
208 purple_notify_error(js->gc->account, NULL, _("Password is required to sign on."), NULL); 135 purple_notify_error(account, NULL, _("Password is required to sign on."), NULL);
209 return FALSE; 136 return;
210 } 137 }
211 138
212 if (remember) 139 if (remember)
213 purple_account_set_remember_password(js->gc->account, TRUE); 140 purple_account_set_remember_password(account, TRUE);
214 141
215 purple_account_set_password(js->gc->account, entry); 142 purple_account_set_password(account, entry);
216
217 return TRUE;
218 }
219
220 static void auth_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
221 {
222 JabberStream *js;
223
224 /* The password prompt dialog doesn't get disposed if the account disconnects */
225 if (!PURPLE_CONNECTION_IS_VALID(conn))
226 return;
227
228 js = conn->proto_data;
229
230 if (!auth_pass_generic(js, fields))
231 return;
232
233 /* Rebuild our callbacks as we now have a password to offer */
234 jabber_sasl_build_callbacks(js);
235
236 /* Restart our connection */
237 jabber_auth_start_cyrus(js);
238 }
239
240 static void
241 auth_old_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields)
242 {
243 JabberStream *js;
244
245 /* The password prompt dialog doesn't get disposed if the account disconnects */
246 if (!PURPLE_CONNECTION_IS_VALID(conn))
247 return;
248
249 js = conn->proto_data;
250
251 if (!auth_pass_generic(js, fields))
252 return;
253 143
254 /* Restart our connection */ 144 /* Restart our connection */
255 jabber_auth_start_old(js); 145 jabber_auth_start_old(js);
256 } 146 }
257 147
258
259 static void 148 static void
260 auth_no_pass_cb(PurpleConnection *conn, PurpleRequestFields *fields) 149 auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
261 { 150 {
262 JabberStream *js;
263
264 /* The password prompt dialog doesn't get disposed if the account disconnects */ 151 /* The password prompt dialog doesn't get disposed if the account disconnects */
265 if (!PURPLE_CONNECTION_IS_VALID(conn)) 152 if (!PURPLE_CONNECTION_IS_VALID(gc))
266 return; 153 return;
267
268 js = conn->proto_data;
269 154
270 /* Disable the account as the user has canceled connecting */ 155 /* Disable the account as the user has canceled connecting */
271 purple_account_set_enabled(conn->account, purple_core_get_ui(), FALSE); 156 purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
272 } 157 }
273
274 static void jabber_auth_start_cyrus(JabberStream *js)
275 {
276 const char *clientout = NULL;
277 char *enc_out;
278 unsigned coutlen = 0;
279 xmlnode *auth;
280 sasl_security_properties_t secprops;
281 gboolean again;
282 gboolean plaintext = TRUE;
283
284 /* Set up security properties and options */
285 secprops.min_ssf = 0;
286 secprops.security_flags = SASL_SEC_NOANONYMOUS;
287
288 if (!jabber_stream_is_ssl(js)) {
289 secprops.max_ssf = -1;
290 secprops.maxbufsize = 4096;
291 plaintext = purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE);
292 if (!plaintext)
293 secprops.security_flags |= SASL_SEC_NOPLAINTEXT;
294 } else {
295 secprops.max_ssf = 0;
296 secprops.maxbufsize = 0;
297 plaintext = TRUE;
298 }
299 secprops.property_names = 0;
300 secprops.property_values = 0;
301
302 do {
303 again = FALSE;
304
305 js->sasl_state = sasl_client_new("xmpp", js->serverFQDN, NULL, NULL, js->sasl_cb, 0, &js->sasl);
306 if (js->sasl_state==SASL_OK) {
307 sasl_setprop(js->sasl, SASL_SEC_PROPS, &secprops);
308 purple_debug_info("sasl", "Mechs found: %s\n", js->sasl_mechs->str);
309 js->sasl_state = sasl_client_start(js->sasl, js->sasl_mechs->str, NULL, &clientout, &coutlen, &js->current_mech);
310 }
311 switch (js->sasl_state) {
312 /* Success */
313 case SASL_OK:
314 case SASL_CONTINUE:
315 break;
316 case SASL_NOMECH:
317 /* No mechanisms have offered to help */
318
319 /* Firstly, if we don't have a password try
320 * to get one
321 */
322
323 if (!purple_account_get_password(js->gc->account)) {
324 purple_account_request_password(js->gc->account, G_CALLBACK(auth_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
325 return;
326
327 /* If we've got a password, but aren't sending
328 * it in plaintext, see if we can turn on
329 * plaintext auth
330 */
331 } else if (!plaintext) {
332 char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
333 js->gc->account->username);
334 purple_request_yes_no(js->gc, _("Plaintext Authentication"),
335 _("Plaintext Authentication"),
336 msg,
337 1, js->gc->account, NULL, NULL, js->gc->account,
338 allow_cyrus_plaintext_auth,
339 disallow_plaintext_auth);
340 g_free(msg);
341 return;
342
343 } else {
344 /* We have no mechs which can work.
345 * Try falling back on the old jabber:iq:auth method. We get here if the server supports
346 * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of
347 * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect
348 * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often
349 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
350 *
351 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
352 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
353 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
354 * which would connect without issue otherwise. -evands
355 */
356 js->auth_type = JABBER_AUTH_IQ_AUTH;
357 jabber_auth_start_old(js);
358 return;
359 }
360 /* not reached */
361 break;
362
363 /* Fatal errors. Give up and go home */
364 case SASL_BADPARAM:
365 case SASL_NOMEM:
366 break;
367
368 /* For everything else, fail the mechanism and try again */
369 default:
370 purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js->sasl_state);
371
372 /*
373 * DAA: is this right?
374 * The manpage says that "mech" will contain the chosen mechanism on success.
375 * Presumably, if we get here that isn't the case and we shouldn't try again?
376 * I suspect that this never happens.
377 */
378 /*
379 * SXW: Yes, this is right. What this handles is the situation where a
380 * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
381 * due to mechanism specific issues, so we want to try one of the other
382 * supported mechanisms. This code handles that case
383 */
384 if (js->current_mech && *js->current_mech) {
385 char *pos;
386 if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) {
387 g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech));
388 }
389 /* Remove space which separated this mech from the next */
390 if ((js->sasl_mechs->str)[0] == ' ') {
391 g_string_erase(js->sasl_mechs, 0, 1);
392 }
393 again = TRUE;
394 }
395
396 sasl_dispose(&js->sasl);
397 }
398 } while (again);
399
400 if (js->sasl_state == SASL_CONTINUE || js->sasl_state == SASL_OK) {
401 auth = xmlnode_new("auth");
402 xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
403 xmlnode_set_attrib(auth, "mechanism", js->current_mech);
404
405 xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
406 xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true");
407
408 if (clientout) {
409 if (coutlen == 0) {
410 xmlnode_insert_data(auth, "=", -1);
411 } else {
412 enc_out = purple_base64_encode((unsigned char*)clientout, coutlen);
413 xmlnode_insert_data(auth, enc_out, -1);
414 g_free(enc_out);
415 }
416 }
417 jabber_send(js, auth);
418 xmlnode_free(auth);
419 } else {
420 purple_connection_error_reason(js->gc,
421 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
422 _("SASL authentication failed"));
423 }
424 }
425
426 static int
427 jabber_sasl_cb_log(void *context, int level, const char *message)
428 {
429 if(level <= SASL_LOG_TRACE)
430 purple_debug_info("sasl", "%s\n", message);
431
432 return SASL_OK;
433 }
434
435 void
436 jabber_sasl_build_callbacks(JabberStream *js)
437 {
438 int id;
439
440 /* Set up our callbacks structure */
441 if (js->sasl_cb == NULL)
442 js->sasl_cb = g_new0(sasl_callback_t,6);
443
444 id = 0;
445 js->sasl_cb[id].id = SASL_CB_GETREALM;
446 js->sasl_cb[id].proc = jabber_sasl_cb_realm;
447 js->sasl_cb[id].context = (void *)js;
448 id++;
449
450 js->sasl_cb[id].id = SASL_CB_AUTHNAME;
451 js->sasl_cb[id].proc = jabber_sasl_cb_simple;
452 js->sasl_cb[id].context = (void *)js;
453 id++;
454
455 js->sasl_cb[id].id = SASL_CB_USER;
456 js->sasl_cb[id].proc = jabber_sasl_cb_simple;
457 js->sasl_cb[id].context = (void *)js;
458 id++;
459
460 if (purple_account_get_password(js->gc->account) != NULL ) {
461 js->sasl_cb[id].id = SASL_CB_PASS;
462 js->sasl_cb[id].proc = jabber_sasl_cb_secret;
463 js->sasl_cb[id].context = (void *)js;
464 id++;
465 }
466
467 js->sasl_cb[id].id = SASL_CB_LOG;
468 js->sasl_cb[id].proc = jabber_sasl_cb_log;
469 js->sasl_cb[id].context = (void*)js;
470 id++;
471
472 js->sasl_cb[id].id = SASL_CB_LIST_END;
473 }
474
475 #endif 158 #endif
476 159
477 void 160 void
478 jabber_auth_start(JabberStream *js, xmlnode *packet) 161 jabber_auth_start(JabberStream *js, xmlnode *packet)
479 { 162 {
480 #ifndef HAVE_CYRUS_SASL 163 GSList *mechanisms = NULL;
481 gboolean digest_md5 = FALSE, plain=FALSE; 164 GSList *l;
482 #endif 165 xmlnode *response = NULL;
483
484 xmlnode *mechs, *mechnode; 166 xmlnode *mechs, *mechnode;
485 167 JabberSaslState state;
168 char *msg = NULL;
486 169
487 if(js->registration) { 170 if(js->registration) {
488 jabber_register_start(js); 171 jabber_register_start(js);
489 return; 172 return;
490 } 173 }
491 174
492 mechs = xmlnode_get_child(packet, "mechanisms"); 175 mechs = xmlnode_get_child(packet, "mechanisms");
493
494 if(!mechs) { 176 if(!mechs) {
495 purple_connection_error_reason(js->gc, 177 purple_connection_error_reason(js->gc,
496 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 178 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
497 _("Invalid response from server")); 179 _("Invalid response from server"));
498 return; 180 return;
499 } 181 }
500
501 #ifdef HAVE_CYRUS_SASL
502 js->sasl_mechs = g_string_new("");
503 #endif
504 182
505 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode; 183 for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode;
506 mechnode = xmlnode_get_next_twin(mechnode)) 184 mechnode = xmlnode_get_next_twin(mechnode))
507 { 185 {
508 char *mech_name = xmlnode_get_data(mechnode); 186 char *mech_name = xmlnode_get_data(mechnode);
509 #ifdef HAVE_CYRUS_SASL 187
510 /* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not 188 if (mech_name && *mech_name)
511 * support it and including it gives a false fall-back to other mechs offerred, 189 mechanisms = g_slist_prepend(mechanisms, mech_name);
512 * leading to incorrect error handling. 190 else if (mech_name)
513 */
514 if (purple_strequal(mech_name, "X-GOOGLE-TOKEN")) {
515 g_free(mech_name); 191 g_free(mech_name);
516 continue; 192
517 } 193 }
518 194
519 g_string_append(js->sasl_mechs, mech_name); 195 for (l = auth_mechs; l; l = l->next) {
520 g_string_append_c(js->sasl_mechs, ' '); 196 JabberSaslMech *possible = l->data;
521 #else 197
522 if (purple_strequal(mech_name, "DIGEST-MD5")) 198 /* Is this the Cyrus SASL mechanism? */
523 digest_md5 = TRUE; 199 if (g_str_equal(possible->name, "*")) {
524 else if (purple_strequal(mech_name, "PLAIN")) 200 js->auth_mech = possible;
525 plain = TRUE; 201 break;
526 #endif 202 }
527 g_free(mech_name); 203
528 } 204 /* Can we find this mechanism in the server's list? */
529 205 if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) {
530 #ifdef HAVE_CYRUS_SASL 206 js->auth_mech = possible;
531 js->auth_type = JABBER_AUTH_CYRUS; 207 break;
532 208 }
533 jabber_sasl_build_callbacks(js); 209 }
534 210
535 jabber_auth_start_cyrus(js); 211 if (js->auth_mech == NULL) {
536 #else 212 /* Found no good mechanisms... */
537
538 if(digest_md5) {
539 xmlnode *auth;
540
541 js->auth_type = JABBER_AUTH_DIGEST_MD5;
542 auth = xmlnode_new("auth");
543 xmlnode_set_namespace(auth, "urn:ietf:params:xml:ns:xmpp-sasl");
544 xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
545
546 jabber_send(js, auth);
547 xmlnode_free(auth);
548 } else if(plain) {
549 js->auth_type = JABBER_AUTH_PLAIN;
550
551 if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) {
552 char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
553 js->gc->account->username);
554 purple_request_yes_no(js->gc, _("Plaintext Authentication"),
555 _("Plaintext Authentication"),
556 msg,
557 1,
558 purple_connection_get_account(js->gc), NULL, NULL,
559 purple_connection_get_account(js->gc), allow_plaintext_auth,
560 disallow_plaintext_auth);
561 g_free(msg);
562 return;
563 }
564 finish_plaintext_authentication(js);
565 } else {
566 purple_connection_error_reason(js->gc, 213 purple_connection_error_reason(js->gc,
567 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, 214 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
568 _("Server does not use any supported authentication method")); 215 _("Server does not use any supported authentication method"));
569 } 216 return;
570 #endif 217 }
218
219 state = js->auth_mech->start(js, mechs, &response, &msg);
220 if (state == JABBER_SASL_STATE_FAIL) {
221 purple_connection_error_reason(js->gc,
222 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
223 msg ? msg : _("Unknown Error"));
224 } else if (response) {
225 jabber_send(js, response);
226 xmlnode_free(response);
227 }
228
229 g_free(msg);
571 } 230 }
572 231
573 static void auth_old_result_cb(JabberStream *js, const char *from, 232 static void auth_old_result_cb(JabberStream *js, const char *from,
574 JabberIqType type, const char *id, 233 JabberIqType type, const char *id,
575 xmlnode *packet, gpointer data) 234 xmlnode *packet, gpointer data)
576 { 235 {
577 if (type == JABBER_IQ_RESULT) { 236 if (type == JABBER_IQ_RESULT) {
578 jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH); 237 jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
579 jabber_disco_items_server(js); 238 jabber_disco_items_server(js);
580 } else { 239 } else {
240 PurpleAccount *account;
581 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; 241 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
582 char *msg = jabber_parse_error(js, packet, &reason); 242 char *msg = jabber_parse_error(js, packet, &reason);
583 xmlnode *error; 243 xmlnode *error;
584 const char *err_code; 244 const char *err_code;
245
246 account = purple_connection_get_account(js->gc);
585 247
586 /* FIXME: Why is this not in jabber_parse_error? */ 248 /* FIXME: Why is this not in jabber_parse_error? */
587 if((error = xmlnode_get_child(packet, "error")) && 249 if((error = xmlnode_get_child(packet, "error")) &&
588 (err_code = xmlnode_get_attrib(error, "code")) && 250 (err_code = xmlnode_get_attrib(error, "code")) &&
589 g_str_equal(err_code, "401")) { 251 g_str_equal(err_code, "401")) {
590 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; 252 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
591 /* Clear the pasword if it isn't being saved */ 253 /* Clear the pasword if it isn't being saved */
592 if (!purple_account_get_remember_password(js->gc->account)) 254 if (!purple_account_get_remember_password(account))
593 purple_account_set_password(js->gc->account, NULL); 255 purple_account_set_password(account, NULL);
594 } 256 }
595 257
596 purple_connection_error_reason(js->gc, reason, msg); 258 purple_connection_error_reason(js->gc, reason, msg);
597 g_free(msg); 259 g_free(msg);
598 } 260 }
661 323
662 jabber_iq_set_callback(iq, auth_old_result_cb, NULL); 324 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
663 jabber_iq_send(iq); 325 jabber_iq_send(iq);
664 326
665 } else if(xmlnode_get_child(query, "password")) { 327 } else if(xmlnode_get_child(query, "password")) {
666 if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(js->gc->account, 328 PurpleAccount *account = purple_connection_get_account(js->gc);
329 if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account,
667 "auth_plain_in_clear", FALSE)) { 330 "auth_plain_in_clear", FALSE)) {
668 char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"), 331 char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
669 js->gc->account->username); 332 purple_account_get_username(account));
670 purple_request_yes_no(js->gc, _("Plaintext Authentication"), 333 purple_request_yes_no(js->gc, _("Plaintext Authentication"),
671 _("Plaintext Authentication"), 334 _("Plaintext Authentication"),
672 msg, 335 msg,
673 1, 336 1,
674 purple_connection_get_account(js->gc), NULL, NULL, 337 account, NULL, NULL,
675 purple_connection_get_account(js->gc), allow_plaintext_auth, 338 account, allow_plaintext_auth,
676 disallow_plaintext_auth); 339 disallow_plaintext_auth);
677 g_free(msg); 340 g_free(msg);
678 return; 341 return;
679 } 342 }
680 finish_plaintext_authentication(js); 343 finish_plaintext_authentication(js);
687 } 350 }
688 } 351 }
689 352
690 void jabber_auth_start_old(JabberStream *js) 353 void jabber_auth_start_old(JabberStream *js)
691 { 354 {
355 PurpleAccount *account;
692 JabberIq *iq; 356 JabberIq *iq;
693 xmlnode *query, *username; 357 xmlnode *query, *username;
358
359 account = purple_connection_get_account(js->gc);
694 360
695 /* 361 /*
696 * We can end up here without encryption if the server doesn't support 362 * We can end up here without encryption if the server doesn't support
697 * <stream:features/> and we're not using old-style SSL. If the user 363 * <stream:features/> and we're not using old-style SSL. If the user
698 * is requiring SSL/TLS, we need to enforce it. 364 * is requiring SSL/TLS, we need to enforce it.
699 */ 365 */
700 if (!jabber_stream_is_ssl(js) && 366 if (!jabber_stream_is_ssl(js) &&
701 purple_account_get_bool(purple_connection_get_account(js->gc), "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { 367 purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) {
702 purple_connection_error_reason(js->gc, 368 purple_connection_error_reason(js->gc,
703 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, 369 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
704 _("You require encryption, but it is not available on this server.")); 370 _("You require encryption, but it is not available on this server."));
371 return;
372 }
373
374 if (js->registration) {
375 jabber_register_start(js);
705 return; 376 return;
706 } 377 }
707 378
708 /* 379 /*
709 * IQ Auth doesn't have support for resource binding, so we need to pick a 380 * IQ Auth doesn't have support for resource binding, so we need to pick a
719 /* If we have Cyrus SASL, then passwords will have been set 390 /* If we have Cyrus SASL, then passwords will have been set
720 * to OPTIONAL for this protocol. So, we need to do our own 391 * to OPTIONAL for this protocol. So, we need to do our own
721 * password prompting here 392 * password prompting here
722 */ 393 */
723 394
724 if (!purple_account_get_password(js->gc->account)) { 395 if (!purple_account_get_password(account)) {
725 purple_account_request_password(js->gc->account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc); 396 purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
726 return; 397 return;
727 } 398 }
728 #endif 399 #endif
729 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth"); 400 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth");
730 401
735 jabber_iq_set_callback(iq, auth_old_cb, NULL); 406 jabber_iq_set_callback(iq, auth_old_cb, NULL);
736 407
737 jabber_iq_send(iq); 408 jabber_iq_send(iq);
738 } 409 }
739 410
740 /* Parts of this algorithm are inspired by stuff in libgsasl */
741 static GHashTable* parse_challenge(const char *challenge)
742 {
743 const char *token_start, *val_start, *val_end, *cur;
744 GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal,
745 g_free, g_free);
746
747 cur = challenge;
748 while(*cur != '\0') {
749 /* Find the end of the token */
750 gboolean in_quotes = FALSE;
751 char *name, *value = NULL;
752 token_start = cur;
753 while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) {
754 if (*cur == '"')
755 in_quotes = !in_quotes;
756 cur++;
757 }
758
759 /* Find start of value. */
760 val_start = strchr(token_start, '=');
761 if (val_start == NULL || val_start > cur)
762 val_start = cur;
763
764 if (token_start != val_start) {
765 name = g_strndup(token_start, val_start - token_start);
766
767 if (val_start != cur) {
768 val_start++;
769 while (val_start != cur && (*val_start == ' ' || *val_start == '\t'
770 || *val_start == '\r' || *val_start == '\n'
771 || *val_start == '"'))
772 val_start++;
773
774 val_end = cur;
775 while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t'
776 || *val_end == '\r' || *val_end == '\n'
777 || *val_end == '"' || *val_end == '\0'))
778 val_end--;
779
780 if (val_start != val_end)
781 value = g_strndup(val_start, val_end - val_start + 1);
782 }
783
784 g_hash_table_replace(ret, name, value);
785 }
786
787 /* Find the start of the next token, if there is one */
788 if (*cur != '\0') {
789 cur++;
790 while (*cur == ' ' || *cur == ',' || *cur == '\t'
791 || *cur == '\r' || *cur == '\n')
792 cur++;
793 }
794 }
795
796 return ret;
797 }
798
799 static char *
800 generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
801 const char *cnonce, const char *a2, const char *realm)
802 {
803 PurpleCipher *cipher;
804 PurpleCipherContext *context;
805 guchar result[16];
806 size_t a1len;
807
808 gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
809
810 if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8",
811 NULL, NULL, NULL)) == NULL) {
812 convnode = g_strdup(jid->node);
813 }
814 if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1",
815 "utf-8", NULL, NULL, NULL)) == NULL)) {
816 convpasswd = g_strdup(passwd);
817 }
818
819 cipher = purple_ciphers_find_cipher("md5");
820 context = purple_cipher_context_new(cipher, NULL);
821
822 x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : "");
823 purple_cipher_context_append(context, (const guchar *)x, strlen(x));
824 purple_cipher_context_digest(context, sizeof(result), result, NULL);
825
826 a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce);
827 a1len = strlen(a1);
828 g_memmove(a1, result, 16);
829
830 purple_cipher_context_reset(context, NULL);
831 purple_cipher_context_append(context, (const guchar *)a1, a1len);
832 purple_cipher_context_digest(context, sizeof(result), result, NULL);
833
834 ha1 = purple_base16_encode(result, 16);
835
836 purple_cipher_context_reset(context, NULL);
837 purple_cipher_context_append(context, (const guchar *)a2, strlen(a2));
838 purple_cipher_context_digest(context, sizeof(result), result, NULL);
839
840 ha2 = purple_base16_encode(result, 16);
841
842 kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2);
843
844 purple_cipher_context_reset(context, NULL);
845 purple_cipher_context_append(context, (const guchar *)kd, strlen(kd));
846 purple_cipher_context_digest(context, sizeof(result), result, NULL);
847 purple_cipher_context_destroy(context);
848
849 z = purple_base16_encode(result, 16);
850
851 g_free(convnode);
852 g_free(convpasswd);
853 g_free(x);
854 g_free(a1);
855 g_free(ha1);
856 g_free(ha2);
857 g_free(kd);
858
859 return z;
860 }
861
862 void 411 void
863 jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet) 412 jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
864 { 413 {
865 414 const char *ns = xmlnode_get_namespace(packet);
866 if(js->auth_type == JABBER_AUTH_DIGEST_MD5) { 415
867 char *enc_in = xmlnode_get_data(packet); 416 if (!purple_strequal(ns, NS_XMPP_SASL)) {
868 char *dec_in; 417 purple_connection_error_reason(js->gc,
869 char *enc_out; 418 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
870 GHashTable *parts; 419 _("Invalid response from server"));
871 420 return;
872 if(!enc_in) { 421 }
422
423 if (js->auth_mech && js->auth_mech->handle_challenge) {
424 xmlnode *response = NULL;
425 char *msg = NULL;
426 JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg);
427 if (state == JABBER_SASL_STATE_FAIL) {
873 purple_connection_error_reason(js->gc, 428 purple_connection_error_reason(js->gc,
874 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 429 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
875 _("Invalid response from server")); 430 msg ? msg : _("Invalid challenge from server"));
876 return; 431 } else if (response) {
877 }
878
879 dec_in = (char *)purple_base64_decode(enc_in, NULL);
880 purple_debug_misc("jabber", "decoded challenge (%"
881 G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in);
882
883 parts = parse_challenge(dec_in);
884
885
886 if (g_hash_table_lookup(parts, "rspauth")) {
887 char *rspauth = g_hash_table_lookup(parts, "rspauth");
888
889
890 if (rspauth && purple_strequal(rspauth, js->expected_rspauth)) {
891 jabber_send_raw(js,
892 "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
893 -1);
894 } else {
895 purple_connection_error_reason(js->gc,
896 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
897 _("Invalid challenge from server"));
898 }
899 g_free(js->expected_rspauth);
900 js->expected_rspauth = NULL;
901 } else {
902 /* assemble a response, and send it */
903 /* see RFC 2831 */
904 char *realm;
905 char *nonce;
906
907 /* Make sure the auth string contains everything that should be there.
908 This isn't everything in RFC2831, but it is what we need. */
909
910 nonce = g_hash_table_lookup(parts, "nonce");
911
912 /* we're actually supposed to prompt the user for a realm if
913 * the server doesn't send one, but that really complicates things,
914 * so i'm not gonna worry about it until is poses a problem to
915 * someone, or I get really bored */
916 realm = g_hash_table_lookup(parts, "realm");
917 if(!realm)
918 realm = js->user->domain;
919
920 if (nonce == NULL || realm == NULL)
921 purple_connection_error_reason(js->gc,
922 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
923 _("Invalid challenge from server"));
924 else {
925 GString *response = g_string_new("");
926 char *a2;
927 char *auth_resp;
928 char *buf;
929 char *cnonce;
930
931 cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL),
932 g_random_int());
933
934 a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm);
935 auth_resp = generate_response_value(js->user,
936 purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
937 g_free(a2);
938
939 a2 = g_strdup_printf(":xmpp/%s", realm);
940 js->expected_rspauth = generate_response_value(js->user,
941 purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
942 g_free(a2);
943
944 g_string_append_printf(response, "username=\"%s\"", js->user->node);
945 g_string_append_printf(response, ",realm=\"%s\"", realm);
946 g_string_append_printf(response, ",nonce=\"%s\"", nonce);
947 g_string_append_printf(response, ",cnonce=\"%s\"", cnonce);
948 g_string_append_printf(response, ",nc=00000001");
949 g_string_append_printf(response, ",qop=auth");
950 g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm);
951 g_string_append_printf(response, ",response=%s", auth_resp);
952 g_string_append_printf(response, ",charset=utf-8");
953
954 g_free(auth_resp);
955 g_free(cnonce);
956
957 enc_out = purple_base64_encode((guchar *)response->str, response->len);
958
959 purple_debug_misc("jabber", "decoded response (%"
960 G_GSIZE_FORMAT "): %s\n",
961 response->len, response->str);
962
963 buf = g_strdup_printf("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>%s</response>", enc_out);
964
965 jabber_send_raw(js, buf, -1);
966
967 g_free(buf);
968
969 g_free(enc_out);
970
971 g_string_free(response, TRUE);
972 }
973 }
974
975 g_free(enc_in);
976 g_free(dec_in);
977 g_hash_table_destroy(parts);
978 }
979 #ifdef HAVE_CYRUS_SASL
980 else if (js->auth_type == JABBER_AUTH_CYRUS) {
981 char *enc_in = xmlnode_get_data(packet);
982 unsigned char *dec_in;
983 char *enc_out;
984 const char *c_out;
985 unsigned int clen;
986 gsize declen;
987 xmlnode *response;
988
989 dec_in = purple_base64_decode(enc_in, &declen);
990
991 js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen,
992 NULL, &c_out, &clen);
993 g_free(enc_in);
994 g_free(dec_in);
995 if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) {
996 gchar *tmp = g_strdup_printf(_("SASL error: %s"),
997 sasl_errdetail(js->sasl));
998 purple_debug_error("jabber", "Error is %d : %s\n",
999 js->sasl_state, sasl_errdetail(js->sasl));
1000 purple_connection_error_reason(js->gc,
1001 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
1002 g_free(tmp);
1003 return;
1004 } else {
1005 response = xmlnode_new("response");
1006 xmlnode_set_namespace(response, "urn:ietf:params:xml:ns:xmpp-sasl");
1007 if (clen > 0) {
1008 /* Cyrus SASL 2.1.22 appears to contain code to add the charset
1009 * to the response for DIGEST-MD5 but there is no possibility
1010 * it will be executed.
1011 *
1012 * My reading of the digestmd5 plugin indicates the username and
1013 * realm are always encoded in UTF-8 (they seem to be the values
1014 * we pass in), so we need to ensure charset=utf-8 is set.
1015 */
1016 if (!purple_strequal(js->current_mech, "DIGEST-MD5") ||
1017 strstr(c_out, ",charset="))
1018 /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
1019 enc_out = purple_base64_encode((unsigned char*)c_out, clen);
1020 else {
1021 char *tmp = g_strdup_printf("%s,charset=utf-8", c_out);
1022 enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14);
1023 g_free(tmp);
1024 }
1025
1026 xmlnode_insert_data(response, enc_out, -1);
1027 g_free(enc_out);
1028 }
1029 jabber_send(js, response); 432 jabber_send(js, response);
1030 xmlnode_free(response); 433 xmlnode_free(response);
1031 } 434 }
1032 } 435
1033 #endif 436 g_free(msg);
437 } else
438 purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
1034 } 439 }
1035 440
1036 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet) 441 void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
1037 { 442 {
1038 const char *ns = xmlnode_get_namespace(packet); 443 const char *ns = xmlnode_get_namespace(packet);
1039 #ifdef HAVE_CYRUS_SASL 444
1040 const void *x; 445 if (!purple_strequal(ns, NS_XMPP_SASL)) {
1041 #endif
1042
1043 if (!purple_strequal(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) {
1044 purple_connection_error_reason(js->gc, 446 purple_connection_error_reason(js->gc,
1045 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 447 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1046 _("Invalid response from server")); 448 _("Invalid response from server"));
1047 return; 449 return;
1048 } 450 }
1049 451
1050 #ifdef HAVE_CYRUS_SASL 452 if (js->auth_mech && js->auth_mech->handle_success) {
1051 /* The SASL docs say that if the client hasn't returned OK yet, we 453 char *msg = NULL;
1052 * should try one more round against it 454 JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
1053 */ 455
1054 if (js->sasl_state != SASL_OK) { 456 if (state == JABBER_SASL_STATE_FAIL) {
1055 char *enc_in = xmlnode_get_data(packet);
1056 unsigned char *dec_in = NULL;
1057 const char *c_out;
1058 unsigned int clen;
1059 gsize declen = 0;
1060
1061 if(enc_in != NULL)
1062 dec_in = purple_base64_decode(enc_in, &declen);
1063
1064 js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen);
1065
1066 g_free(enc_in);
1067 g_free(dec_in);
1068
1069 if (js->sasl_state != SASL_OK) {
1070 /* This should never happen! */
1071 purple_connection_error_reason(js->gc, 457 purple_connection_error_reason(js->gc,
1072 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 458 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1073 _("Invalid response from server")); 459 msg ? msg : _("Invalid response from server"));
1074 g_return_if_reached(); 460 return;
1075 } 461 } else if (state == JABBER_SASL_STATE_CONTINUE) {
1076 } 462 purple_connection_error_reason(js->gc,
1077 /* If we've negotiated a security layer, we need to enable it */ 463 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
1078 if (js->sasl) { 464 msg ? msg : _("Server thinks authentication is complete, but client does not"));
1079 sasl_getprop(js->sasl, SASL_SSF, &x); 465 return;
1080 if (*(int *)x > 0) { 466 }
1081 sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); 467
1082 js->sasl_maxbuf = *(int *)x; 468 g_free(msg);
1083 } 469 }
1084 }
1085 #endif
1086 470
1087 /* 471 /*
1088 * The stream will be reinitialized later in jabber_recv_cb_ssl() or 472 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
1089 * jabber_bosh_connection_send. 473 * jabber_bosh_connection_send.
1090 */ 474 */
1093 } 477 }
1094 478
1095 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) 479 void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet)
1096 { 480 {
1097 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; 481 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1098 char *msg; 482 char *msg = NULL;
1099 483
1100 #ifdef HAVE_CYRUS_SASL 484 if (js->auth_mech && js->auth_mech->handle_failure) {
1101 if(js->auth_fail_count++ < 5) { 485 xmlnode *stanza = NULL;
1102 if (js->current_mech && *js->current_mech) { 486 JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg);
1103 char *pos; 487
1104 if ((pos = strstr(js->sasl_mechs->str, js->current_mech))) { 488 if (state != JABBER_SASL_STATE_FAIL && stanza) {
1105 g_string_erase(js->sasl_mechs, pos-js->sasl_mechs->str, strlen(js->current_mech)); 489 jabber_send(js, stanza);
1106 } 490 xmlnode_free(stanza);
1107 /* Remove space which separated this mech from the next */
1108 if ((js->sasl_mechs->str)[0] == ' ') {
1109 g_string_erase(js->sasl_mechs, 0, 1);
1110 }
1111 }
1112 if (*js->sasl_mechs->str) {
1113 /* If we have remaining mechs to try, do so */
1114 sasl_dispose(&js->sasl);
1115
1116 jabber_auth_start_cyrus(js);
1117 return; 491 return;
1118 } 492 }
1119 } 493 }
1120 #endif 494
1121 msg = jabber_parse_error(js, packet, &reason); 495 if (!msg)
1122 if(!msg) { 496 msg = jabber_parse_error(js, packet, &reason);
497
498 if (!msg) {
1123 purple_connection_error_reason(js->gc, 499 purple_connection_error_reason(js->gc,
1124 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 500 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1125 _("Invalid response from server")); 501 _("Invalid response from server"));
1126 } else { 502 } else {
1127 purple_connection_error_reason(js->gc, reason, msg); 503 purple_connection_error_reason(js->gc, reason, msg);
1128 g_free(msg); 504 g_free(msg);
1129 } 505 }
1130 } 506 }
507
508 static gint compare_mech(gconstpointer a, gconstpointer b)
509 {
510 const JabberSaslMech *mech_a = a;
511 const JabberSaslMech *mech_b = b;
512
513 /* higher priority comes *before* lower priority in the list */
514 if (mech_a->priority > mech_b->priority)
515 return -1;
516 else if (mech_a->priority < mech_b->priority)
517 return 1;
518 /* This really shouldn't happen */
519 return 0;
520 }
521
522 void jabber_auth_init(void)
523 {
524 JabberSaslMech **tmp;
525 gint count, i;
526
527 auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech);
528 auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech);
529 #ifdef HAVE_CYRUS_SASL
530 auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech);
531 #endif
532
533 tmp = jabber_auth_get_scram_mechs(&count);
534 for (i = 0; i < count; ++i)
535 auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech);
536 }
537
538 void jabber_auth_uninit(void)
539 {
540 g_slist_free(auth_mechs);
541 auth_mechs = NULL;
542 }