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
31 static JabberSaslState
jabber_auth_start_cyrus(JabberStream
*js
, PurpleXmlNode
**reply
,
33 static void jabber_sasl_build_callbacks(JabberStream
*);
35 static void disallow_plaintext_auth(PurpleAccount
*account
)
37 purple_connection_error(purple_account_get_connection(account
),
38 PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
39 _("Server may require plaintext authentication over an unencrypted stream"));
42 static void start_cyrus_wrapper(JabberStream
*js
)
45 PurpleXmlNode
*response
= NULL
;
46 JabberSaslState state
= jabber_auth_start_cyrus(js
, &response
, &error
);
48 if (state
== JABBER_SASL_STATE_FAIL
) {
49 purple_connection_error(js
->gc
,
50 PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
53 } else if (response
) {
54 jabber_send(js
, response
);
55 purple_xmlnode_free(response
);
60 /* Callbacks for Cyrus SASL */
62 static int jabber_sasl_cb_realm(void *ctx
, int id
, const char **avail
, const char **result
)
64 JabberStream
*js
= ctx
;
66 if (id
!= SASL_CB_GETREALM
|| !result
) return SASL_BADPARAM
;
68 *result
= js
->user
->domain
;
73 static int jabber_sasl_cb_simple(void *ctx
, int id
, const char **res
, unsigned *len
)
75 JabberStream
*js
= ctx
;
78 case SASL_CB_AUTHNAME
:
79 *res
= js
->user
->node
;
87 if (len
) *len
= strlen((char *)*res
);
91 static int jabber_sasl_cb_secret(sasl_conn_t
*conn
, void *ctx
, int id
, sasl_secret_t
**secret
)
93 JabberStream
*js
= ctx
;
96 if (!conn
|| !secret
|| id
!= SASL_CB_PASS
)
99 len
= strlen(js
->sasl_password
);
100 /* Not an off-by-one because sasl_secret_t defines char data[1] */
101 /* TODO: This can probably be moved to glib's allocator */
102 js
->sasl_secret
= malloc(sizeof(sasl_secret_t
) + len
);
103 if (!js
->sasl_secret
)
106 js
->sasl_secret
->len
= len
;
107 strcpy((char*)js
->sasl_secret
->data
, js
->sasl_password
);
109 *secret
= js
->sasl_secret
;
113 static void allow_cyrus_plaintext_auth(PurpleAccount
*account
)
115 PurpleConnection
*gc
;
118 gc
= purple_account_get_connection(account
);
119 js
= purple_connection_get_protocol_data(gc
);
121 purple_account_set_bool(account
, "auth_plain_in_clear", TRUE
);
123 start_cyrus_wrapper(js
);
126 static void auth_pass_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
128 PurpleAccount
*account
;
133 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
134 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
136 account
= purple_connection_get_account(gc
);
137 js
= purple_connection_get_protocol_data(gc
);
139 entry
= purple_request_fields_get_string(fields
, "password");
140 remember
= purple_request_fields_get_bool(fields
, "remember");
142 if (!entry
|| !*entry
)
144 purple_notify_error(account
, NULL
,
145 _("Password is required to sign on."), NULL
,
146 purple_request_cpar_from_connection(gc
));
151 purple_account_set_remember_password(account
, TRUE
);
153 purple_account_set_password(account
, entry
, NULL
, NULL
);
155 js
->sasl_password
= g_strdup(entry
);
157 /* Rebuild our callbacks as we now have a password to offer */
158 jabber_sasl_build_callbacks(js
);
160 /* Restart our negotiation */
161 start_cyrus_wrapper(js
);
165 auth_no_pass_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
167 PurpleAccount
*account
;
169 /* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
170 PURPLE_ASSERT_CONNECTION_IS_VALID(gc
);
172 account
= purple_connection_get_account(gc
);
174 /* Disable the account as the user has cancelled connecting */
175 purple_account_set_enabled(account
, purple_core_get_ui(), FALSE
);
178 static gboolean
remove_current_mech(JabberStream
*js
) {
180 if ((pos
= strstr(js
->sasl_mechs
->str
, js
->current_mech
))) {
181 size_t len
= strlen(js
->current_mech
);
182 /* Clean up space that separated this Mech from the one before or after it */
183 if (pos
> js
->sasl_mechs
->str
&& *(pos
- 1) == ' ') {
184 /* Handle removing space before when current_mech isn't the first mech in the list */
187 } else if (strlen(pos
) > len
&& *(pos
+ len
) == ' ') {
188 /* Handle removing space after */
191 g_string_erase(js
->sasl_mechs
, pos
- js
->sasl_mechs
->str
, len
);
197 static JabberSaslState
198 jabber_auth_start_cyrus(JabberStream
*js
, PurpleXmlNode
**reply
, char **error
)
200 PurpleAccount
*account
;
201 const char *clientout
= NULL
;
203 unsigned coutlen
= 0;
204 sasl_security_properties_t secprops
;
206 gboolean plaintext
= TRUE
;
208 /* Set up security properties and options */
209 secprops
.min_ssf
= 0;
210 secprops
.security_flags
= SASL_SEC_NOANONYMOUS
;
212 account
= purple_connection_get_account(js
->gc
);
214 if (!jabber_stream_is_ssl(js
)) {
215 secprops
.max_ssf
= -1;
216 secprops
.maxbufsize
= 4096;
217 plaintext
= purple_account_get_bool(account
, "auth_plain_in_clear", FALSE
);
219 secprops
.security_flags
|= SASL_SEC_NOPLAINTEXT
;
221 secprops
.max_ssf
= 0;
222 secprops
.maxbufsize
= 0;
225 secprops
.property_names
= 0;
226 secprops
.property_values
= 0;
231 js
->sasl_state
= sasl_client_new("xmpp", js
->serverFQDN
, NULL
, NULL
, js
->sasl_cb
, 0, &js
->sasl
);
232 if (js
->sasl_state
==SASL_OK
) {
233 sasl_setprop(js
->sasl
, SASL_SEC_PROPS
, &secprops
);
234 purple_debug_info("sasl", "Mechs found: %s\n", js
->sasl_mechs
->str
);
235 js
->sasl_state
= sasl_client_start(js
->sasl
, js
->sasl_mechs
->str
, NULL
, &clientout
, &coutlen
, &js
->current_mech
);
237 switch (js
->sasl_state
) {
243 /* No mechanisms have offered to help */
245 /* Firstly, if we don't have a password try
249 if (!js
->sasl_password
) {
250 purple_account_request_password(account
, G_CALLBACK(auth_pass_cb
), G_CALLBACK(auth_no_pass_cb
), js
->gc
);
251 return JABBER_SASL_STATE_CONTINUE
;
253 /* If we've got a password, but aren't sending
254 * it in plaintext, see if we can turn on
257 /* XXX Should we just check for PLAIN/LOGIN being offered mechanisms? */
258 } else if (!plaintext
) {
259 char *msg
= g_strdup_printf(_("%s may require plaintext authentication over an unencrypted connection. Allow this and continue authentication?"),
260 purple_account_get_username(account
));
261 purple_request_yes_no(js
->gc
, _("Plaintext Authentication"),
262 _("Plaintext Authentication"),
264 1, purple_request_cpar_from_account(account
), account
,
265 allow_cyrus_plaintext_auth
,
266 disallow_plaintext_auth
);
268 return JABBER_SASL_STATE_CONTINUE
;
271 js
->auth_fail_count
++;
273 if (js
->auth_fail_count
== 1 &&
274 (js
->sasl_mechs
->str
&& g_str_equal(js
->sasl_mechs
->str
, "GSSAPI"))) {
275 /* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
276 * for compatibility with iChat 10.5 Server and other jabberd based servers.
278 * iChat Server 10.5 and certain other corporate servers offer SASL GSSAPI by default, which is often
279 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
281 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
282 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
283 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
284 * which would connect without issue otherwise. -evands
286 js
->auth_mech
= NULL
;
287 jabber_auth_start_old(js
);
288 return JABBER_SASL_STATE_CONTINUE
;
293 /* Fatal errors. Give up and go home */
296 *error
= g_strdup(_("SASL authentication failed"));
299 /* For everything else, fail the mechanism and try again */
301 purple_debug_info("sasl", "sasl_state is %d, failing the mech and trying again\n", js
->sasl_state
);
303 js
->auth_fail_count
++;
306 * DAA: is this right?
307 * The manpage says that "mech" will contain the chosen mechanism on success.
308 * Presumably, if we get here that isn't the case and we shouldn't try again?
309 * I suspect that this never happens.
312 * SXW: Yes, this is right. What this handles is the situation where a
313 * mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
314 * due to mechanism specific issues, so we want to try one of the other
315 * supported mechanisms. This code handles that case
317 if (js
->current_mech
&& *js
->current_mech
) {
318 remove_current_mech(js
);
319 /* Should we only try again if we've removed the mech? */
323 sasl_dispose(&js
->sasl
);
327 if (js
->sasl_state
== SASL_CONTINUE
|| js
->sasl_state
== SASL_OK
) {
328 PurpleXmlNode
*auth
= purple_xmlnode_new("auth");
329 purple_xmlnode_set_namespace(auth
, NS_XMPP_SASL
);
330 purple_xmlnode_set_attrib(auth
, "mechanism", js
->current_mech
);
332 purple_xmlnode_set_attrib(auth
, "xmlns:ga", "http://www.google.com/talk/protocol/auth");
333 purple_xmlnode_set_attrib(auth
, "ga:client-uses-full-bind-result", "true");
337 purple_xmlnode_insert_data(auth
, "=", -1);
339 enc_out
= purple_base64_encode((unsigned char*)clientout
, coutlen
);
340 purple_xmlnode_insert_data(auth
, enc_out
, -1);
346 return JABBER_SASL_STATE_CONTINUE
;
348 return JABBER_SASL_STATE_FAIL
;
353 jabber_sasl_cb_log(void *context
, int level
, const char *message
)
355 if(level
<= SASL_LOG_TRACE
)
356 purple_debug_info("sasl", "%s\n", message
);
362 jabber_sasl_build_callbacks(JabberStream
*js
)
366 /* Set up our callbacks structure */
367 if (js
->sasl_cb
== NULL
)
368 js
->sasl_cb
= g_new0(sasl_callback_t
,6);
371 js
->sasl_cb
[id
].id
= SASL_CB_GETREALM
;
372 js
->sasl_cb
[id
].proc
= (void *)jabber_sasl_cb_realm
;
373 js
->sasl_cb
[id
].context
= (void *)js
;
376 js
->sasl_cb
[id
].id
= SASL_CB_AUTHNAME
;
377 js
->sasl_cb
[id
].proc
= (void *)jabber_sasl_cb_simple
;
378 js
->sasl_cb
[id
].context
= (void *)js
;
381 js
->sasl_cb
[id
].id
= SASL_CB_USER
;
382 js
->sasl_cb
[id
].proc
= (void *)jabber_sasl_cb_simple
;
383 js
->sasl_cb
[id
].context
= (void *)js
;
386 if (js
->sasl_password
!= NULL
) {
387 js
->sasl_cb
[id
].id
= SASL_CB_PASS
;
388 js
->sasl_cb
[id
].proc
= (void *)jabber_sasl_cb_secret
;
389 js
->sasl_cb
[id
].context
= (void *)js
;
393 js
->sasl_cb
[id
].id
= SASL_CB_LOG
;
394 js
->sasl_cb
[id
].proc
= (void *)jabber_sasl_cb_log
;
395 js
->sasl_cb
[id
].context
= (void*)js
;
398 js
->sasl_cb
[id
].id
= SASL_CB_LIST_END
;
401 static JabberSaslState
402 jabber_cyrus_start(JabberStream
*js
, PurpleXmlNode
*mechanisms
,
403 PurpleXmlNode
**reply
, char **error
)
405 PurpleXmlNode
*mechnode
;
408 js
->sasl_mechs
= g_string_new("");
409 js
->sasl_password
= g_strdup(purple_connection_get_password(js
->gc
));
411 for(mechnode
= purple_xmlnode_get_child(mechanisms
, "mechanism"); mechnode
;
412 mechnode
= purple_xmlnode_get_next_twin(mechnode
))
414 char *mech_name
= purple_xmlnode_get_data(mechnode
);
416 /* Ignore blank mechanisms and EXTERNAL. External isn't
417 * supported, and Cyrus SASL's mechanism returns
418 * SASL_NOMECH when the caller (us) doesn't configure it.
419 * Except SASL_NOMECH is supposed to mean "no concordant
420 * mechanisms"... Easiest just to blacklist it (for now).
422 if (!mech_name
|| !*mech_name
||
423 g_str_equal(mech_name
, "EXTERNAL")) {
428 g_string_append(js
->sasl_mechs
, mech_name
);
429 g_string_append_c(js
->sasl_mechs
, ' ');
433 /* Strip off the trailing ' ' */
434 if (js
->sasl_mechs
->len
> 1)
435 g_string_truncate(js
->sasl_mechs
, js
->sasl_mechs
->len
- 1);
437 jabber_sasl_build_callbacks(js
);
438 ret
= jabber_auth_start_cyrus(js
, reply
, error
);
441 * Triggered if no overlap between server and client
442 * supported mechanisms.
444 if (ret
== JABBER_SASL_STATE_FAIL
&& *error
== NULL
)
445 *error
= g_strdup(_("Server does not use any supported authentication method"));
450 static JabberSaslState
451 jabber_cyrus_handle_challenge(JabberStream
*js
, PurpleXmlNode
*packet
,
452 PurpleXmlNode
**reply
, char **error
)
454 char *enc_in
= purple_xmlnode_get_data(packet
);
455 unsigned char *dec_in
;
461 dec_in
= purple_base64_decode(enc_in
, &declen
);
463 js
->sasl_state
= sasl_client_step(js
->sasl
, (char*)dec_in
, declen
,
464 NULL
, &c_out
, &clen
);
467 if (js
->sasl_state
!= SASL_CONTINUE
&& js
->sasl_state
!= SASL_OK
) {
468 gchar
*tmp
= g_strdup_printf(_("SASL error: %s"),
469 sasl_errdetail(js
->sasl
));
470 purple_debug_error("jabber", "Error is %d : %s\n",
471 js
->sasl_state
, sasl_errdetail(js
->sasl
));
473 return JABBER_SASL_STATE_FAIL
;
475 PurpleXmlNode
*response
= purple_xmlnode_new("response");
476 purple_xmlnode_set_namespace(response
, NS_XMPP_SASL
);
478 /* Cyrus SASL 2.1.22 appears to contain code to add the charset
479 * to the response for DIGEST-MD5 but there is no possibility
480 * it will be executed.
482 * My reading of the digestmd5 plugin indicates the username and
483 * realm are always encoded in UTF-8 (they seem to be the values
484 * we pass in), so we need to ensure charset=utf-8 is set.
486 if (!purple_strequal(js
->current_mech
, "DIGEST-MD5") ||
487 strstr(c_out
, ",charset="))
488 /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
489 enc_out
= purple_base64_encode((unsigned char*)c_out
, clen
);
491 char *tmp
= g_strdup_printf("%s,charset=utf-8", c_out
);
492 enc_out
= purple_base64_encode((unsigned char*)tmp
, clen
+ 14);
496 purple_xmlnode_insert_data(response
, enc_out
, -1);
501 return JABBER_SASL_STATE_CONTINUE
;
505 static JabberSaslState
506 jabber_cyrus_handle_success(JabberStream
*js
, PurpleXmlNode
*packet
,
511 /* The SASL docs say that if the client hasn't returned OK yet, we
512 * should try one more round against it
514 if (js
->sasl_state
!= SASL_OK
) {
515 char *enc_in
= purple_xmlnode_get_data(packet
);
516 unsigned char *dec_in
= NULL
;
522 dec_in
= purple_base64_decode(enc_in
, &declen
);
524 js
->sasl_state
= sasl_client_step(js
->sasl
, (char*)dec_in
, declen
, NULL
, &c_out
, &clen
);
529 if (js
->sasl_state
!= SASL_OK
) {
530 /* This happens when the server sends back jibberish
531 * in the "additional data with success" case.
532 * Seen with Wildfire 3.0.1.
534 *error
= g_strdup(_("Invalid response from server"));
535 return JABBER_SASL_STATE_FAIL
;
539 /* If we've negotiated a security layer, we need to enable it */
541 sasl_getprop(js
->sasl
, SASL_SSF
, &x
);
543 sasl_getprop(js
->sasl
, SASL_MAXOUTBUF
, &x
);
544 js
->sasl_maxbuf
= *(int *)x
;
548 return JABBER_SASL_STATE_OK
;
551 static JabberSaslState
552 jabber_cyrus_handle_failure(JabberStream
*js
, PurpleXmlNode
*packet
,
553 PurpleXmlNode
**reply
, char **error
)
555 if (js
->auth_fail_count
++ < 5) {
556 if (js
->current_mech
&& *js
->current_mech
) {
557 remove_current_mech(js
);
560 /* Should we only try again if we've actually removed a mech? */
561 if (*js
->sasl_mechs
->str
) {
562 /* If we have remaining mechs to try, do so */
563 sasl_dispose(&js
->sasl
);
565 return jabber_auth_start_cyrus(js
, reply
, error
);
567 } else if ((js
->auth_fail_count
== 1) &&
568 (js
->current_mech
&& g_str_equal(js
->current_mech
, "GSSAPI"))) {
569 /* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
570 * for compatibility with iChat 10.5 Server and other jabberd based servers.
572 * iChat Server 10.5 and certain other corporate servers offer SASL GSSAPI by default, which is often
573 * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
575 * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
576 * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
577 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
578 * which would connect without issue otherwise. -evands
580 sasl_dispose(&js
->sasl
);
582 js
->auth_mech
= NULL
;
583 jabber_auth_start_old(js
);
584 return JABBER_SASL_STATE_CONTINUE
;
588 /* Nothing to send */
589 return JABBER_SASL_STATE_FAIL
;
592 static JabberSaslMech cyrus_mech
= {
594 "*", /* name; Cyrus provides a bunch of mechanisms, so use an invalid
595 * mechanism name (per rfc4422 3.1). */
597 jabber_cyrus_handle_challenge
,
598 jabber_cyrus_handle_success
,
599 jabber_cyrus_handle_failure
,
603 JabberSaslMech
*jabber_auth_get_cyrus_mech(void)