rename accountopt.[ch] to purpleaccountoption.[ch]
[pidgin-git.git] / libpurple / protocols / jabber / auth.c
blob08cff69edaf6f2156652ab946c836a375a860e09
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 static GSList *auth_mechs = NULL;
43 static void auth_old_result_cb(JabberStream *js, const char *from,
44 JabberIqType type, const char *id,
45 PurpleXmlNode *packet, gpointer data);
47 static void finish_plaintext_authentication(JabberStream *js)
49 JabberIq *iq;
50 PurpleXmlNode *query, *x;
52 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
53 query = purple_xmlnode_get_child(iq->node, "query");
54 x = purple_xmlnode_new_child(query, "username");
55 purple_xmlnode_insert_data(x, js->user->node, -1);
56 x = purple_xmlnode_new_child(query, "resource");
57 purple_xmlnode_insert_data(x, js->user->resource, -1);
58 x = purple_xmlnode_new_child(query, "password");
59 purple_xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
60 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
61 jabber_iq_send(iq);
64 static void allow_plaintext_auth(PurpleAccount *account)
66 PurpleConnection *gc;
67 JabberStream *js;
69 purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
71 gc = purple_account_get_connection(account);
72 js = purple_connection_get_protocol_data(gc);
74 finish_plaintext_authentication(js);
77 static void disallow_plaintext_auth(PurpleAccount *account)
79 purple_connection_error(purple_account_get_connection(account),
80 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
81 _("Server requires plaintext authentication over an unencrypted stream"));
84 #ifdef HAVE_CYRUS_SASL
85 static void
86 auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
88 PurpleAccount *account;
89 JabberStream *js;
90 const char *entry;
91 gboolean remember;
93 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
94 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
96 account = purple_connection_get_account(gc);
97 js = purple_connection_get_protocol_data(gc);
99 entry = purple_request_fields_get_string(fields, "password");
100 remember = purple_request_fields_get_bool(fields, "remember");
102 if (!entry || !*entry)
104 purple_notify_error(account, NULL,
105 _("Password is required to sign on."), NULL,
106 purple_request_cpar_from_connection(gc));
107 return;
110 if (remember)
111 purple_account_set_remember_password(account, TRUE);
113 purple_account_set_password(account, entry, NULL, NULL);
115 /* Restart our connection */
116 jabber_auth_start_old(js);
119 static void
120 auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
122 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
123 PURPLE_ASSERT_CONNECTION_IS_VALID(gc);
125 /* Disable the account as the user has cancelled connecting */
126 purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
128 #endif
130 void
131 jabber_auth_start(JabberStream *js, PurpleXmlNode *packet)
133 GSList *mechanisms = NULL;
134 GSList *l;
135 PurpleXmlNode *response = NULL;
136 PurpleXmlNode *mechs, *mechnode;
137 JabberSaslState state;
138 char *msg = NULL;
140 if(js->registration) {
141 jabber_register_start(js);
142 return;
145 mechs = purple_xmlnode_get_child(packet, "mechanisms");
146 if(!mechs) {
147 purple_connection_error(js->gc,
148 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
149 _("Invalid response from server"));
150 return;
153 for(mechnode = purple_xmlnode_get_child(mechs, "mechanism"); mechnode;
154 mechnode = purple_xmlnode_get_next_twin(mechnode))
156 char *mech_name = purple_xmlnode_get_data(mechnode);
158 if (mech_name && *mech_name)
159 mechanisms = g_slist_prepend(mechanisms, mech_name);
160 else
161 g_free(mech_name);
165 for (l = auth_mechs; l; l = l->next) {
166 JabberSaslMech *possible = l->data;
168 /* Is this the Cyrus SASL mechanism? */
169 if (purple_strequal(possible->name, "*")) {
170 js->auth_mech = possible;
171 break;
174 /* Can we find this mechanism in the server's list? */
175 if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) {
176 js->auth_mech = possible;
177 break;
181 while (mechanisms) {
182 g_free(mechanisms->data);
183 mechanisms = g_slist_delete_link(mechanisms, mechanisms);
186 if (js->auth_mech == NULL) {
187 /* Found no good mechanisms... */
188 purple_connection_error(js->gc,
189 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
190 _("Server does not use any supported authentication method"));
191 return;
194 state = js->auth_mech->start(js, mechs, &response, &msg);
195 if (state == JABBER_SASL_STATE_FAIL) {
196 purple_connection_error(js->gc,
197 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
198 msg ? msg : _("Unknown Error"));
199 } else if (response) {
200 jabber_send(js, response);
201 purple_xmlnode_free(response);
204 g_free(msg);
207 static void auth_old_result_cb(JabberStream *js, const char *from,
208 JabberIqType type, const char *id,
209 PurpleXmlNode *packet, gpointer data)
211 if (type == JABBER_IQ_RESULT) {
212 jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
213 jabber_disco_items_server(js);
214 } else {
215 PurpleAccount *account;
216 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
217 char *msg = jabber_parse_error(js, packet, &reason);
218 PurpleXmlNode *error;
219 const char *err_code;
221 account = purple_connection_get_account(js->gc);
223 /* FIXME: Why is this not in jabber_parse_error? */
224 if((error = purple_xmlnode_get_child(packet, "error")) &&
225 (err_code = purple_xmlnode_get_attrib(error, "code")) &&
226 purple_strequal(err_code, "401")) {
227 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
228 /* Clear the pasword if it isn't being saved */
229 if (!purple_account_get_remember_password(account))
230 purple_account_set_password(account, NULL, NULL, NULL);
233 purple_connection_error(js->gc, reason, msg);
234 g_free(msg);
238 static void auth_old_cb(JabberStream *js, const char *from,
239 JabberIqType type, const char *id,
240 PurpleXmlNode *packet, gpointer data)
242 JabberIq *iq;
243 PurpleXmlNode *query, *x;
244 const char *pw = purple_connection_get_password(js->gc);
246 if (type == JABBER_IQ_ERROR) {
247 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
248 char *msg = jabber_parse_error(js, packet, &reason);
249 purple_connection_error(js->gc, reason, msg);
250 g_free(msg);
251 } else if (type == JABBER_IQ_RESULT) {
252 query = purple_xmlnode_get_child(packet, "query");
253 if (js->stream_id && *js->stream_id &&
254 purple_xmlnode_get_child(query, "digest")) {
255 char *s, *hash;
257 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
258 query = purple_xmlnode_get_child(iq->node, "query");
259 x = purple_xmlnode_new_child(query, "username");
260 purple_xmlnode_insert_data(x, js->user->node, -1);
261 x = purple_xmlnode_new_child(query, "resource");
262 purple_xmlnode_insert_data(x, js->user->resource, -1);
264 x = purple_xmlnode_new_child(query, "digest");
265 s = g_strdup_printf("%s%s", js->stream_id, pw);
266 hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1,
267 s, -1);
268 purple_xmlnode_insert_data(x, hash, -1);
269 g_free(hash);
270 g_free(s);
271 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
272 jabber_iq_send(iq);
273 } else if ((x = purple_xmlnode_get_child(query, "crammd5"))) {
274 /* For future reference, this appears to be a custom OS X extension
275 * to non-SASL authentication.
277 const char *challenge;
278 gchar *digest;
280 /* Calculate the MHAC-MD5 digest */
281 challenge = purple_xmlnode_get_attrib(x, "challenge");
282 digest = g_compute_hmac_for_string(G_CHECKSUM_MD5,
283 (guchar *)pw, strlen(pw),
284 challenge, -1);
286 g_return_if_fail(digest != NULL);
288 /* Create the response query */
289 iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
290 query = purple_xmlnode_get_child(iq->node, "query");
292 x = purple_xmlnode_new_child(query, "username");
293 purple_xmlnode_insert_data(x, js->user->node, -1);
294 x = purple_xmlnode_new_child(query, "resource");
295 purple_xmlnode_insert_data(x, js->user->resource, -1);
297 x = purple_xmlnode_new_child(query, "crammd5");
299 purple_xmlnode_insert_data(x, digest, 32);
300 g_free(digest);
302 jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
303 jabber_iq_send(iq);
305 } else if(purple_xmlnode_get_child(query, "password")) {
306 PurpleAccount *account = purple_connection_get_account(js->gc);
307 if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account,
308 "auth_plain_in_clear", FALSE)) {
309 char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
310 purple_account_get_username(account));
311 purple_request_yes_no(js->gc, _("Plaintext Authentication"),
312 _("Plaintext Authentication"),
313 msg,
315 purple_request_cpar_from_account(account),
316 account, allow_plaintext_auth,
317 disallow_plaintext_auth);
318 g_free(msg);
319 return;
321 finish_plaintext_authentication(js);
322 } else {
323 purple_connection_error(js->gc,
324 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
325 _("Server does not use any supported authentication method"));
326 return;
331 void jabber_auth_start_old(JabberStream *js)
333 PurpleAccount *account;
334 JabberIq *iq;
335 PurpleXmlNode *query, *username;
337 account = purple_connection_get_account(js->gc);
340 * We can end up here without encryption if the server doesn't support
341 * <stream:features/> and we're not using old-style SSL. If the user
342 * is requiring SSL/TLS, we need to enforce it.
344 if (!jabber_stream_is_ssl(js) &&
345 purple_strequal("require_tls",
346 purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
347 purple_connection_error(js->gc,
348 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
349 _("You require encryption, but it is not available on this server."));
350 return;
353 if (js->registration) {
354 jabber_register_start(js);
355 return;
359 * IQ Auth doesn't have support for resource binding, so we need to pick a
360 * default resource so it will work properly. jabberd14 throws an error and
361 * iChat server just fails silently.
363 if (!js->user->resource || *js->user->resource == '\0') {
364 g_free(js->user->resource);
365 js->user->resource = g_strdup("Home");
368 #ifdef HAVE_CYRUS_SASL
369 /* If we have Cyrus SASL, then passwords will have been set
370 * to OPTIONAL for this protocol. So, we need to do our own
371 * password prompting here
374 if (!purple_connection_get_password(js->gc)) {
375 purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
376 return;
378 #endif
379 iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth");
381 query = purple_xmlnode_get_child(iq->node, "query");
382 username = purple_xmlnode_new_child(query, "username");
383 purple_xmlnode_insert_data(username, js->user->node, -1);
385 jabber_iq_set_callback(iq, auth_old_cb, NULL);
387 jabber_iq_send(iq);
390 void
391 jabber_auth_handle_challenge(JabberStream *js, PurpleXmlNode *packet)
393 const char *ns = purple_xmlnode_get_namespace(packet);
395 if (!purple_strequal(ns, NS_XMPP_SASL)) {
396 purple_connection_error(js->gc,
397 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
398 _("Invalid response from server"));
399 return;
402 if (js->auth_mech && js->auth_mech->handle_challenge) {
403 PurpleXmlNode *response = NULL;
404 char *msg = NULL;
405 JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg);
406 if (state == JABBER_SASL_STATE_FAIL) {
407 purple_connection_error(js->gc,
408 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
409 msg ? msg : _("Invalid challenge from server"));
410 } else if (response) {
411 jabber_send(js, response);
412 purple_xmlnode_free(response);
415 g_free(msg);
416 } else
417 purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
420 void jabber_auth_handle_success(JabberStream *js, PurpleXmlNode *packet)
422 const char *ns = purple_xmlnode_get_namespace(packet);
424 if (!purple_strequal(ns, NS_XMPP_SASL)) {
425 purple_connection_error(js->gc,
426 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
427 _("Invalid response from server"));
428 return;
431 if (js->auth_mech && js->auth_mech->handle_success) {
432 char *msg = NULL;
433 JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
435 if (state == JABBER_SASL_STATE_FAIL) {
436 purple_connection_error(js->gc,
437 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
438 msg ? msg : _("Invalid response from server"));
439 return;
440 } else if (state == JABBER_SASL_STATE_CONTINUE) {
441 purple_connection_error(js->gc,
442 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
443 msg ? msg : _("Server thinks authentication is complete, but client does not"));
444 return;
447 g_free(msg);
451 * The stream will be reinitialized later in jabber_recv_cb_ssl() or
452 * jabber_bosh_connection_send.
454 js->reinit = TRUE;
455 jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
458 void jabber_auth_handle_failure(JabberStream *js, PurpleXmlNode *packet)
460 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
461 char *msg = NULL;
463 if (js->auth_mech && js->auth_mech->handle_failure) {
464 PurpleXmlNode *stanza = NULL;
465 JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg);
467 if (state != JABBER_SASL_STATE_FAIL) {
468 if (stanza) {
469 jabber_send(js, stanza);
470 purple_xmlnode_free(stanza);
473 return;
477 if (!msg)
478 msg = jabber_parse_error(js, packet, &reason);
480 if (!msg) {
481 purple_connection_error(js->gc,
482 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
483 _("Invalid response from server"));
484 } else {
485 purple_connection_error(js->gc, reason, msg);
486 g_free(msg);
490 static gint compare_mech(gconstpointer a, gconstpointer b)
492 const JabberSaslMech *mech_a = a;
493 const JabberSaslMech *mech_b = b;
495 /* higher priority comes *before* lower priority in the list */
496 if (mech_a->priority > mech_b->priority)
497 return -1;
498 else if (mech_a->priority < mech_b->priority)
499 return 1;
500 /* This really shouldn't happen */
501 return 0;
504 void jabber_auth_add_mech(JabberSaslMech *mech)
506 auth_mechs = g_slist_insert_sorted(auth_mechs, mech, compare_mech);
509 void jabber_auth_remove_mech(JabberSaslMech *mech)
511 auth_mechs = g_slist_remove(auth_mechs, mech);
514 void jabber_auth_init(void)
516 JabberSaslMech **tmp;
517 gint count, i;
519 jabber_auth_add_mech(jabber_auth_get_plain_mech());
520 jabber_auth_add_mech(jabber_auth_get_digest_md5_mech());
521 #ifdef HAVE_CYRUS_SASL
522 jabber_auth_add_mech(jabber_auth_get_cyrus_mech());
523 #endif
524 #ifdef HAVE_WEBEX_TOKEN
525 jabber_auth_add_mech(jabber_auth_get_webex_token_mech());
526 #endif
528 tmp = jabber_auth_get_scram_mechs(&count);
529 for (i = 0; i < count; ++i)
530 jabber_auth_add_mech(tmp[i]);
533 void jabber_auth_uninit(void)
535 g_slist_free(auth_mechs);
536 auth_mechs = NULL;