Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / jabber / auth.c
blobdb0e6ed020b4c2dd8963baaade6b1d48ced88e35
1 /*
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
6 * source distribution.
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
23 #include "internal.h"
25 #include "account.h"
26 #include "debug.h"
27 #include "core.h"
28 #include "conversation.h"
29 #include "request.h"
30 #include "sslconn.h"
31 #include "util.h"
32 #include "xmlnode.h"
34 #include "auth.h"
35 #include "disco.h"
36 #include "jabber.h"
37 #include "jutil.h"
38 #include "iq.h"
39 #include "notify.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)
52 JabberIq *iq;
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);
64 jabber_iq_send(iq);
67 static void allow_plaintext_auth(PurpleAccount *account)
69 PurpleConnection *gc;
70 JabberStream *js;
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
88 static void
89 auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
91 PurpleAccount *account;
92 JabberStream *js;
93 const char *entry;
94 gboolean remember;
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));
110 return;
113 if (remember)
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);
122 static void
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);
131 #endif
133 void
134 jabber_auth_start(JabberStream *js, PurpleXmlNode *packet)
136 GSList *mechanisms = NULL;
137 GSList *l;
138 PurpleXmlNode *response = NULL;
139 PurpleXmlNode *mechs, *mechnode;
140 JabberSaslState state;
141 char *msg = NULL;
143 if(js->registration) {
144 jabber_register_start(js);
145 return;
148 mechs = purple_xmlnode_get_child(packet, "mechanisms");
149 if(!mechs) {
150 purple_connection_error(js->gc,
151 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
152 _("Invalid response from server"));
153 return;
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);
163 else
164 g_free(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;
174 break;
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;
180 break;
184 while (mechanisms) {
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"));
194 return;
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);
207 g_free(msg);
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);
217 } else {
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);
237 g_free(msg);
241 static void auth_old_cb(JabberStream *js, const char *from,
242 JabberIqType type, const char *id,
243 PurpleXmlNode *packet, gpointer data)
245 JabberIq *iq;
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);
253 g_free(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")) {
258 char *s, *hash;
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);
271 g_free(hash);
272 g_free(s);
273 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
274 jabber_iq_send(iq);
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;
280 gchar digest[33];
281 PurpleCipher *hmac;
282 PurpleHash *md5;
283 gssize diglen;
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);
293 g_object_unref(md5);
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);
311 jabber_iq_send(iq);
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"),
321 msg,
323 purple_request_cpar_from_account(account),
324 account, allow_plaintext_auth,
325 disallow_plaintext_auth);
326 g_free(msg);
327 return;
329 finish_plaintext_authentication(js);
330 } else {
331 purple_connection_error(js->gc,
332 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
333 _("Server does not use any supported authentication method"));
334 return;
339 void jabber_auth_start_old(JabberStream *js)
341 PurpleAccount *account;
342 JabberIq *iq;
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."));
358 return;
361 if (js->registration) {
362 jabber_register_start(js);
363 return;
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);
384 return;
386 #endif
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);
395 jabber_iq_send(iq);
398 void
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"));
407 return;
410 if (js->auth_mech && js->auth_mech->handle_challenge) {
411 PurpleXmlNode *response = NULL;
412 char *msg = 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);
423 g_free(msg);
424 } else
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"));
436 return;
439 if (js->auth_mech && js->auth_mech->handle_success) {
440 char *msg = NULL;
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"));
447 return;
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"));
452 return;
455 g_free(msg);
459 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
460 * jabber_bosh_connection_send.
462 js->reinit = TRUE;
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;
469 char *msg = NULL;
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) {
476 if (stanza) {
477 jabber_send(js, stanza);
478 purple_xmlnode_free(stanza);
481 return;
485 if (!msg)
486 msg = jabber_parse_error(js, packet, &reason);
488 if (!msg) {
489 purple_connection_error(js->gc,
490 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
491 _("Invalid response from server"));
492 } else {
493 purple_connection_error(js->gc, reason, msg);
494 g_free(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)
505 return -1;
506 else if (mech_a->priority < mech_b->priority)
507 return 1;
508 /* This really shouldn't happen */
509 return 0;
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;
525 gint count, i;
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());
531 #endif
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);
541 auth_mechs = NULL;