2 * purple - Jabber Protocol Plugin
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "conversation.h"
41 #include "ciphers/hmaccipher.h"
42 #include "ciphers/md5hash.h"
44 static GSList
*auth_mechs
= NULL
;
46 static void auth_old_result_cb(JabberStream
*js
, const char *from
,
47 JabberIqType type
, const char *id
,
48 PurpleXmlNode
*packet
, gpointer data
);
50 static void finish_plaintext_authentication(JabberStream
*js
)
53 PurpleXmlNode
*query
, *x
;
55 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:auth");
56 query
= purple_xmlnode_get_child(iq
->node
, "query");
57 x
= purple_xmlnode_new_child(query
, "username");
58 purple_xmlnode_insert_data(x
, js
->user
->node
, -1);
59 x
= purple_xmlnode_new_child(query
, "resource");
60 purple_xmlnode_insert_data(x
, js
->user
->resource
, -1);
61 x
= purple_xmlnode_new_child(query
, "password");
62 purple_xmlnode_insert_data(x
, purple_connection_get_password(js
->gc
), -1);
63 jabber_iq_set_callback(iq
, auth_old_result_cb
, NULL
);
67 static void allow_plaintext_auth(PurpleAccount
*account
)
72 purple_account_set_bool(account
, "auth_plain_in_clear", TRUE
);
74 gc
= purple_account_get_connection(account
);
75 js
= purple_connection_get_protocol_data(gc
);
77 finish_plaintext_authentication(js
);
80 static void disallow_plaintext_auth(PurpleAccount
*account
)
82 purple_connection_error(purple_account_get_connection(account
),
83 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
84 _("Server requires plaintext authentication over an unencrypted stream"));
87 #ifdef HAVE_CYRUS_SASL
89 auth_old_pass_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
91 PurpleAccount
*account
;
96 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
97 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
99 account
= purple_connection_get_account(gc
);
100 js
= purple_connection_get_protocol_data(gc
);
102 entry
= purple_request_fields_get_string(fields
, "password");
103 remember
= purple_request_fields_get_bool(fields
, "remember");
105 if (!entry
|| !*entry
)
107 purple_notify_error(account
, NULL
,
108 _("Password is required to sign on."), NULL
,
109 purple_request_cpar_from_connection(gc
));
114 purple_account_set_remember_password(account
, TRUE
);
116 purple_account_set_password(account
, entry
, NULL
, NULL
);
118 /* Restart our connection */
119 jabber_auth_start_old(js
);
123 auth_no_pass_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
125 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
126 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
128 /* Disable the account as the user has cancelled connecting */
129 purple_account_set_enabled(purple_connection_get_account(gc
), purple_core_get_ui(), FALSE
);
134 jabber_auth_start(JabberStream
*js
, PurpleXmlNode
*packet
)
136 GSList
*mechanisms
= NULL
;
138 PurpleXmlNode
*response
= NULL
;
139 PurpleXmlNode
*mechs
, *mechnode
;
140 JabberSaslState state
;
143 if(js
->registration
) {
144 jabber_register_start(js
);
148 mechs
= purple_xmlnode_get_child(packet
, "mechanisms");
150 purple_connection_error(js
->gc
,
151 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
152 _("Invalid response from server"));
156 for(mechnode
= purple_xmlnode_get_child(mechs
, "mechanism"); mechnode
;
157 mechnode
= purple_xmlnode_get_next_twin(mechnode
))
159 char *mech_name
= purple_xmlnode_get_data(mechnode
);
161 if (mech_name
&& *mech_name
)
162 mechanisms
= g_slist_prepend(mechanisms
, mech_name
);
168 for (l
= auth_mechs
; l
; l
= l
->next
) {
169 JabberSaslMech
*possible
= l
->data
;
171 /* Is this the Cyrus SASL mechanism? */
172 if (g_str_equal(possible
->name
, "*")) {
173 js
->auth_mech
= possible
;
177 /* Can we find this mechanism in the server's list? */
178 if (g_slist_find_custom(mechanisms
, possible
->name
, (GCompareFunc
)strcmp
)) {
179 js
->auth_mech
= possible
;
185 g_free(mechanisms
->data
);
186 mechanisms
= g_slist_delete_link(mechanisms
, mechanisms
);
189 if (js
->auth_mech
== NULL
) {
190 /* Found no good mechanisms... */
191 purple_connection_error(js
->gc
,
192 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
193 _("Server does not use any supported authentication method"));
197 state
= js
->auth_mech
->start(js
, mechs
, &response
, &msg
);
198 if (state
== JABBER_SASL_STATE_FAIL
) {
199 purple_connection_error(js
->gc
,
200 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
201 msg
? msg
: _("Unknown Error"));
202 } else if (response
) {
203 jabber_send(js
, response
);
204 purple_xmlnode_free(response
);
210 static void auth_old_result_cb(JabberStream
*js
, const char *from
,
211 JabberIqType type
, const char *id
,
212 PurpleXmlNode
*packet
, gpointer data
)
214 if (type
== JABBER_IQ_RESULT
) {
215 jabber_stream_set_state(js
, JABBER_STREAM_POST_AUTH
);
216 jabber_disco_items_server(js
);
218 PurpleAccount
*account
;
219 PurpleConnectionError reason
= PURPLE_CONNECTION_ERROR_NETWORK_ERROR
;
220 char *msg
= jabber_parse_error(js
, packet
, &reason
);
221 PurpleXmlNode
*error
;
222 const char *err_code
;
224 account
= purple_connection_get_account(js
->gc
);
226 /* FIXME: Why is this not in jabber_parse_error? */
227 if((error
= purple_xmlnode_get_child(packet
, "error")) &&
228 (err_code
= purple_xmlnode_get_attrib(error
, "code")) &&
229 g_str_equal(err_code
, "401")) {
230 reason
= PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED
;
231 /* Clear the pasword if it isn't being saved */
232 if (!purple_account_get_remember_password(account
))
233 purple_account_set_password(account
, NULL
, NULL
, NULL
);
236 purple_connection_error(js
->gc
, reason
, msg
);
241 static void auth_old_cb(JabberStream
*js
, const char *from
,
242 JabberIqType type
, const char *id
,
243 PurpleXmlNode
*packet
, gpointer data
)
246 PurpleXmlNode
*query
, *x
;
247 const char *pw
= purple_connection_get_password(js
->gc
);
249 if (type
== JABBER_IQ_ERROR
) {
250 PurpleConnectionError reason
= PURPLE_CONNECTION_ERROR_NETWORK_ERROR
;
251 char *msg
= jabber_parse_error(js
, packet
, &reason
);
252 purple_connection_error(js
->gc
, reason
, msg
);
254 } else if (type
== JABBER_IQ_RESULT
) {
255 query
= purple_xmlnode_get_child(packet
, "query");
256 if (js
->stream_id
&& *js
->stream_id
&&
257 purple_xmlnode_get_child(query
, "digest")) {
260 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:auth");
261 query
= purple_xmlnode_get_child(iq
->node
, "query");
262 x
= purple_xmlnode_new_child(query
, "username");
263 purple_xmlnode_insert_data(x
, js
->user
->node
, -1);
264 x
= purple_xmlnode_new_child(query
, "resource");
265 purple_xmlnode_insert_data(x
, js
->user
->resource
, -1);
267 x
= purple_xmlnode_new_child(query
, "digest");
268 s
= g_strdup_printf("%s%s", js
->stream_id
, pw
);
269 hash
= jabber_calculate_data_hash(s
, strlen(s
), "sha1");
270 purple_xmlnode_insert_data(x
, hash
, -1);
273 jabber_iq_set_callback(iq
, auth_old_result_cb
, NULL
);
275 } else if ((x
= purple_xmlnode_get_child(query
, "crammd5"))) {
276 /* For future reference, this appears to be a custom OS X extension
277 * to non-SASL authentication.
279 const char *challenge
;
285 /* Calculate the MHAC-MD5 digest */
286 md5
= purple_md5_hash_new();
287 hmac
= purple_hmac_cipher_new(md5
);
288 challenge
= purple_xmlnode_get_attrib(x
, "challenge");
289 purple_cipher_set_key(hmac
, (guchar
*)pw
, strlen(pw
));
290 purple_cipher_append(hmac
, (guchar
*)challenge
, strlen(challenge
));
291 diglen
= purple_cipher_digest_to_str(hmac
, digest
, 33);
292 g_object_unref(hmac
);
295 g_return_if_fail(diglen
> 0);
297 /* Create the response query */
298 iq
= jabber_iq_new_query(js
, JABBER_IQ_SET
, "jabber:iq:auth");
299 query
= purple_xmlnode_get_child(iq
->node
, "query");
301 x
= purple_xmlnode_new_child(query
, "username");
302 purple_xmlnode_insert_data(x
, js
->user
->node
, -1);
303 x
= purple_xmlnode_new_child(query
, "resource");
304 purple_xmlnode_insert_data(x
, js
->user
->resource
, -1);
306 x
= purple_xmlnode_new_child(query
, "crammd5");
308 purple_xmlnode_insert_data(x
, digest
, 32);
310 jabber_iq_set_callback(iq
, auth_old_result_cb
, NULL
);
313 } else if(purple_xmlnode_get_child(query
, "password")) {
314 PurpleAccount
*account
= purple_connection_get_account(js
->gc
);
315 if(!jabber_stream_is_ssl(js
) && !purple_account_get_bool(account
,
316 "auth_plain_in_clear", FALSE
)) {
317 char *msg
= g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
318 purple_account_get_username(account
));
319 purple_request_yes_no(js
->gc
, _("Plaintext Authentication"),
320 _("Plaintext Authentication"),
323 purple_request_cpar_from_account(account
),
324 account
, allow_plaintext_auth
,
325 disallow_plaintext_auth
);
329 finish_plaintext_authentication(js
);
331 purple_connection_error(js
->gc
,
332 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
333 _("Server does not use any supported authentication method"));
339 void jabber_auth_start_old(JabberStream
*js
)
341 PurpleAccount
*account
;
343 PurpleXmlNode
*query
, *username
;
345 account
= purple_connection_get_account(js
->gc
);
348 * We can end up here without encryption if the server doesn't support
349 * <stream:features/> and we're not using old-style SSL. If the user
350 * is requiring SSL/TLS, we need to enforce it.
352 if (!jabber_stream_is_ssl(js
) &&
353 g_str_equal("require_tls",
354 purple_account_get_string(account
, "connection_security", JABBER_DEFAULT_REQUIRE_TLS
))) {
355 purple_connection_error(js
->gc
,
356 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
357 _("You require encryption, but it is not available on this server."));
361 if (js
->registration
) {
362 jabber_register_start(js
);
367 * IQ Auth doesn't have support for resource binding, so we need to pick a
368 * default resource so it will work properly. jabberd14 throws an error and
369 * iChat server just fails silently.
371 if (!js
->user
->resource
|| *js
->user
->resource
== '\0') {
372 g_free(js
->user
->resource
);
373 js
->user
->resource
= g_strdup("Home");
376 #ifdef HAVE_CYRUS_SASL
377 /* If we have Cyrus SASL, then passwords will have been set
378 * to OPTIONAL for this protocol. So, we need to do our own
379 * password prompting here
382 if (!purple_connection_get_password(js
->gc
)) {
383 purple_account_request_password(account
, G_CALLBACK(auth_old_pass_cb
), G_CALLBACK(auth_no_pass_cb
), js
->gc
);
387 iq
= jabber_iq_new_query(js
, JABBER_IQ_GET
, "jabber:iq:auth");
389 query
= purple_xmlnode_get_child(iq
->node
, "query");
390 username
= purple_xmlnode_new_child(query
, "username");
391 purple_xmlnode_insert_data(username
, js
->user
->node
, -1);
393 jabber_iq_set_callback(iq
, auth_old_cb
, NULL
);
399 jabber_auth_handle_challenge(JabberStream
*js
, PurpleXmlNode
*packet
)
401 const char *ns
= purple_xmlnode_get_namespace(packet
);
403 if (!purple_strequal(ns
, NS_XMPP_SASL
)) {
404 purple_connection_error(js
->gc
,
405 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
406 _("Invalid response from server"));
410 if (js
->auth_mech
&& js
->auth_mech
->handle_challenge
) {
411 PurpleXmlNode
*response
= NULL
;
413 JabberSaslState state
= js
->auth_mech
->handle_challenge(js
, packet
, &response
, &msg
);
414 if (state
== JABBER_SASL_STATE_FAIL
) {
415 purple_connection_error(js
->gc
,
416 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
417 msg
? msg
: _("Invalid challenge from server"));
418 } else if (response
) {
419 jabber_send(js
, response
);
420 purple_xmlnode_free(response
);
425 purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
428 void jabber_auth_handle_success(JabberStream
*js
, PurpleXmlNode
*packet
)
430 const char *ns
= purple_xmlnode_get_namespace(packet
);
432 if (!purple_strequal(ns
, NS_XMPP_SASL
)) {
433 purple_connection_error(js
->gc
,
434 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
435 _("Invalid response from server"));
439 if (js
->auth_mech
&& js
->auth_mech
->handle_success
) {
441 JabberSaslState state
= js
->auth_mech
->handle_success(js
, packet
, &msg
);
443 if (state
== JABBER_SASL_STATE_FAIL
) {
444 purple_connection_error(js
->gc
,
445 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
446 msg
? msg
: _("Invalid response from server"));
448 } else if (state
== JABBER_SASL_STATE_CONTINUE
) {
449 purple_connection_error(js
->gc
,
450 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
451 msg
? msg
: _("Server thinks authentication is complete, but client does not"));
459 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
460 * jabber_bosh_connection_send.
463 jabber_stream_set_state(js
, JABBER_STREAM_POST_AUTH
);
466 void jabber_auth_handle_failure(JabberStream
*js
, PurpleXmlNode
*packet
)
468 PurpleConnectionError reason
= PURPLE_CONNECTION_ERROR_NETWORK_ERROR
;
471 if (js
->auth_mech
&& js
->auth_mech
->handle_failure
) {
472 PurpleXmlNode
*stanza
= NULL
;
473 JabberSaslState state
= js
->auth_mech
->handle_failure(js
, packet
, &stanza
, &msg
);
475 if (state
!= JABBER_SASL_STATE_FAIL
) {
477 jabber_send(js
, stanza
);
478 purple_xmlnode_free(stanza
);
486 msg
= jabber_parse_error(js
, packet
, &reason
);
489 purple_connection_error(js
->gc
,
490 PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
491 _("Invalid response from server"));
493 purple_connection_error(js
->gc
, reason
, msg
);
498 static gint
compare_mech(gconstpointer a
, gconstpointer b
)
500 const JabberSaslMech
*mech_a
= a
;
501 const JabberSaslMech
*mech_b
= b
;
503 /* higher priority comes *before* lower priority in the list */
504 if (mech_a
->priority
> mech_b
->priority
)
506 else if (mech_a
->priority
< mech_b
->priority
)
508 /* This really shouldn't happen */
512 void jabber_auth_add_mech(JabberSaslMech
*mech
)
514 auth_mechs
= g_slist_insert_sorted(auth_mechs
, mech
, compare_mech
);
517 void jabber_auth_remove_mech(JabberSaslMech
*mech
)
519 auth_mechs
= g_slist_remove(auth_mechs
, mech
);
522 void jabber_auth_init(void)
524 JabberSaslMech
**tmp
;
527 jabber_auth_add_mech(jabber_auth_get_plain_mech());
528 jabber_auth_add_mech(jabber_auth_get_digest_md5_mech());
529 #ifdef HAVE_CYRUS_SASL
530 jabber_auth_add_mech(jabber_auth_get_cyrus_mech());
533 tmp
= jabber_auth_get_scram_mechs(&count
);
534 for (i
= 0; i
< count
; ++i
)
535 jabber_auth_add_mech(tmp
[i
]);
538 void jabber_auth_uninit(void)
540 g_slist_free(auth_mechs
);