Mercurial > pidgin.yaz
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 } |