pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Fix an error caused by misreading purple_strequal()
2019-11-05, John Bailey
e74e34093dfa
Fix an error caused by misreading purple_strequal()
/*
* purple - Jabber Protocol Plugin
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include
"internal.h"
#include
"core.h"
#include
"debug.h"
#include
"request.h"
#include
"auth.h"
#include
"jabber.h"
static
JabberSaslState
jabber_auth_start_cyrus
(
JabberStream
*
js
,
PurpleXmlNode
**
reply
,
char
**
error
);
static
void
jabber_sasl_build_callbacks
(
JabberStream
*
);
static
void
disallow_plaintext_auth
(
PurpleAccount
*
account
)
{
purple_connection_error
(
purple_account_get_connection
(
account
),
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
_
(
"Server may require plaintext authentication over an unencrypted stream"
));
}
static
void
start_cyrus_wrapper
(
JabberStream
*
js
)
{
char
*
error
=
NULL
;
PurpleXmlNode
*
response
=
NULL
;
JabberSaslState
state
=
jabber_auth_start_cyrus
(
js
,
&
response
,
&
error
);
if
(
state
==
JABBER_SASL_STATE_FAIL
)
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
,
error
);
g_free
(
error
);
}
else
if
(
response
)
{
jabber_send
(
js
,
response
);
purple_xmlnode_free
(
response
);
}
}
/* Callbacks for Cyrus SASL */
static
int
jabber_sasl_cb_realm
(
void
*
ctx
,
int
id
,
const
char
**
avail
,
const
char
**
result
)
{
JabberStream
*
js
=
ctx
;
if
(
id
!=
SASL_CB_GETREALM
||
!
result
)
return
SASL_BADPARAM
;
*
result
=
js
->
user
->
domain
;
return
SASL_OK
;
}
static
int
jabber_sasl_cb_simple
(
void
*
ctx
,
int
id
,
const
char
**
res
,
unsigned
*
len
)
{
JabberStream
*
js
=
ctx
;
switch
(
id
)
{
case
SASL_CB_AUTHNAME
:
*
res
=
js
->
user
->
node
;
break
;
case
SASL_CB_USER
:
*
res
=
""
;
break
;
default
:
return
SASL_BADPARAM
;
}
if
(
len
)
*
len
=
strlen
((
char
*
)
*
res
);
return
SASL_OK
;
}
static
int
jabber_sasl_cb_secret
(
sasl_conn_t
*
conn
,
void
*
ctx
,
int
id
,
sasl_secret_t
**
secret
)
{
JabberStream
*
js
=
ctx
;
size_t
len
;
if
(
!
conn
||
!
secret
||
id
!=
SASL_CB_PASS
)
return
SASL_BADPARAM
;
len
=
strlen
(
js
->
sasl_password
);
/* Not an off-by-one because sasl_secret_t defines char data[1] */
/* TODO: This can probably be moved to glib's allocator */
js
->
sasl_secret
=
malloc
(
sizeof
(
sasl_secret_t
)
+
len
);
if
(
!
js
->
sasl_secret
)
return
SASL_NOMEM
;
js
->
sasl_secret
->
len
=
len
;
strcpy
((
char
*
)
js
->
sasl_secret
->
data
,
js
->
sasl_password
);
*
secret
=
js
->
sasl_secret
;
return
SASL_OK
;
}
static
void
allow_cyrus_plaintext_auth
(
PurpleAccount
*
account
)
{
PurpleConnection
*
gc
;
JabberStream
*
js
;
gc
=
purple_account_get_connection
(
account
);
js
=
purple_connection_get_protocol_data
(
gc
);
purple_account_set_bool
(
account
,
"auth_plain_in_clear"
,
TRUE
);
start_cyrus_wrapper
(
js
);
}
static
void
auth_pass_cb
(
PurpleConnection
*
gc
,
PurpleRequestFields
*
fields
)
{
PurpleAccount
*
account
;
JabberStream
*
js
;
const
char
*
entry
;
gboolean
remember
;
/* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
PURPLE_ASSERT_CONNECTION_IS_VALID
(
gc
);
account
=
purple_connection_get_account
(
gc
);
js
=
purple_connection_get_protocol_data
(
gc
);
entry
=
purple_request_fields_get_string
(
fields
,
"password"
);
remember
=
purple_request_fields_get_bool
(
fields
,
"remember"
);
if
(
!
entry
||
!*
entry
)
{
purple_notify_error
(
account
,
NULL
,
_
(
"Password is required to sign on."
),
NULL
,
purple_request_cpar_from_connection
(
gc
));
return
;
}
if
(
remember
)
purple_account_set_remember_password
(
account
,
TRUE
);
purple_account_set_password
(
account
,
entry
,
NULL
,
NULL
);
js
->
sasl_password
=
g_strdup
(
entry
);
/* Rebuild our callbacks as we now have a password to offer */
jabber_sasl_build_callbacks
(
js
);
/* Restart our negotiation */
start_cyrus_wrapper
(
js
);
}
static
void
auth_no_pass_cb
(
PurpleConnection
*
gc
,
PurpleRequestFields
*
fields
)
{
PurpleAccount
*
account
;
/* TODO: the password prompt dialog doesn't get disposed if the account disconnects */
PURPLE_ASSERT_CONNECTION_IS_VALID
(
gc
);
account
=
purple_connection_get_account
(
gc
);
/* Disable the account as the user has cancelled connecting */
purple_account_set_enabled
(
account
,
purple_core_get_ui
(),
FALSE
);
}
static
gboolean
remove_current_mech
(
JabberStream
*
js
)
{
char
*
pos
;
if
((
pos
=
strstr
(
js
->
sasl_mechs
->
str
,
js
->
current_mech
)))
{
size_t
len
=
strlen
(
js
->
current_mech
);
/* Clean up space that separated this Mech from the one before or after it */
if
(
pos
>
js
->
sasl_mechs
->
str
&&
*
(
pos
-
1
)
==
' '
)
{
/* Handle removing space before when current_mech isn't the first mech in the list */
pos
--
;
len
++
;
}
else
if
(
strlen
(
pos
)
>
len
&&
*
(
pos
+
len
)
==
' '
)
{
/* Handle removing space after */
len
++
;
}
g_string_erase
(
js
->
sasl_mechs
,
pos
-
js
->
sasl_mechs
->
str
,
len
);
return
TRUE
;
}
return
FALSE
;
}
static
JabberSaslState
jabber_auth_start_cyrus
(
JabberStream
*
js
,
PurpleXmlNode
**
reply
,
char
**
error
)
{
PurpleAccount
*
account
;
const
char
*
clientout
=
NULL
;
char
*
enc_out
;
unsigned
coutlen
=
0
;
sasl_security_properties_t
secprops
;
gboolean
again
;
gboolean
plaintext
=
TRUE
;
/* Set up security properties and options */
secprops
.
min_ssf
=
0
;
secprops
.
security_flags
=
SASL_SEC_NOANONYMOUS
;
account
=
purple_connection_get_account
(
js
->
gc
);
if
(
!
jabber_stream_is_ssl
(
js
))
{
secprops
.
max_ssf
=
-1
;
secprops
.
maxbufsize
=
4096
;
plaintext
=
purple_account_get_bool
(
account
,
"auth_plain_in_clear"
,
FALSE
);
if
(
!
plaintext
)
secprops
.
security_flags
|=
SASL_SEC_NOPLAINTEXT
;
}
else
{
secprops
.
max_ssf
=
0
;
secprops
.
maxbufsize
=
0
;
plaintext
=
TRUE
;
}
secprops
.
property_names
=
0
;
secprops
.
property_values
=
0
;
do
{
again
=
FALSE
;
js
->
sasl_state
=
sasl_client_new
(
"xmpp"
,
js
->
serverFQDN
,
NULL
,
NULL
,
js
->
sasl_cb
,
0
,
&
js
->
sasl
);
if
(
js
->
sasl_state
==
SASL_OK
)
{
sasl_setprop
(
js
->
sasl
,
SASL_SEC_PROPS
,
&
secprops
);
purple_debug_info
(
"sasl"
,
"Mechs found: %s
\n
"
,
js
->
sasl_mechs
->
str
);
js
->
sasl_state
=
sasl_client_start
(
js
->
sasl
,
js
->
sasl_mechs
->
str
,
NULL
,
&
clientout
,
&
coutlen
,
&
js
->
current_mech
);
}
switch
(
js
->
sasl_state
)
{
/* Success */
case
SASL_OK
:
case
SASL_CONTINUE
:
break
;
case
SASL_NOMECH
:
/* No mechanisms have offered to help */
/* Firstly, if we don't have a password try
* to get one
*/
if
(
!
js
->
sasl_password
)
{
purple_account_request_password
(
account
,
G_CALLBACK
(
auth_pass_cb
),
G_CALLBACK
(
auth_no_pass_cb
),
js
->
gc
);
return
JABBER_SASL_STATE_CONTINUE
;
/* If we've got a password, but aren't sending
* it in plaintext, see if we can turn on
* plaintext auth
*/
/* XXX Should we just check for PLAIN/LOGIN being offered mechanisms? */
}
else
if
(
!
plaintext
)
{
char
*
msg
=
g_strdup_printf
(
_
(
"%s may require plaintext authentication over an unencrypted connection. Allow this and continue authentication?"
),
purple_account_get_username
(
account
));
purple_request_yes_no
(
js
->
gc
,
_
(
"Plaintext Authentication"
),
_
(
"Plaintext Authentication"
),
msg
,
1
,
purple_request_cpar_from_account
(
account
),
account
,
allow_cyrus_plaintext_auth
,
disallow_plaintext_auth
);
g_free
(
msg
);
return
JABBER_SASL_STATE_CONTINUE
;
}
else
js
->
auth_fail_count
++
;
if
(
js
->
auth_fail_count
==
1
&&
purple_strequal
(
js
->
sasl_mechs
->
str
,
"GSSAPI"
))
{
/* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
* for compatibility with iChat 10.5 Server and other jabberd based servers.
*
* iChat Server 10.5 and certain other corporate servers offer SASL GSSAPI by default, which is often
* not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
*
* Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
* I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
* Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
* which would connect without issue otherwise. -evands
*/
js
->
auth_mech
=
NULL
;
jabber_auth_start_old
(
js
);
return
JABBER_SASL_STATE_CONTINUE
;
}
break
;
/* Fatal errors. Give up and go home */
case
SASL_BADPARAM
:
case
SASL_NOMEM
:
*
error
=
g_strdup
(
_
(
"SASL authentication failed"
));
break
;
/* For everything else, fail the mechanism and try again */
default
:
purple_debug_info
(
"sasl"
,
"sasl_state is %d, failing the mech and trying again
\n
"
,
js
->
sasl_state
);
js
->
auth_fail_count
++
;
/*
* DAA: is this right?
* The manpage says that "mech" will contain the chosen mechanism on success.
* Presumably, if we get here that isn't the case and we shouldn't try again?
* I suspect that this never happens.
*/
/*
* SXW: Yes, this is right. What this handles is the situation where a
* mechanism, say GSSAPI, is tried. If that mechanism fails, it may be
* due to mechanism specific issues, so we want to try one of the other
* supported mechanisms. This code handles that case
*/
if
(
js
->
current_mech
&&
*
js
->
current_mech
)
{
remove_current_mech
(
js
);
/* Should we only try again if we've removed the mech? */
again
=
TRUE
;
}
sasl_dispose
(
&
js
->
sasl
);
}
}
while
(
again
);
if
(
js
->
sasl_state
==
SASL_CONTINUE
||
js
->
sasl_state
==
SASL_OK
)
{
PurpleXmlNode
*
auth
=
purple_xmlnode_new
(
"auth"
);
purple_xmlnode_set_namespace
(
auth
,
NS_XMPP_SASL
);
purple_xmlnode_set_attrib
(
auth
,
"mechanism"
,
js
->
current_mech
);
purple_xmlnode_set_attrib
(
auth
,
"xmlns:ga"
,
"http://www.google.com/talk/protocol/auth"
);
purple_xmlnode_set_attrib
(
auth
,
"ga:client-uses-full-bind-result"
,
"true"
);
if
(
clientout
)
{
if
(
coutlen
==
0
)
{
purple_xmlnode_insert_data
(
auth
,
"="
,
-1
);
}
else
{
enc_out
=
g_base64_encode
((
unsigned
char
*
)
clientout
,
coutlen
);
purple_xmlnode_insert_data
(
auth
,
enc_out
,
-1
);
g_free
(
enc_out
);
}
}
*
reply
=
auth
;
return
JABBER_SASL_STATE_CONTINUE
;
}
else
{
return
JABBER_SASL_STATE_FAIL
;
}
}
static
int
jabber_sasl_cb_log
(
void
*
context
,
int
level
,
const
char
*
message
)
{
if
(
level
<=
SASL_LOG_TRACE
)
purple_debug_info
(
"sasl"
,
"%s
\n
"
,
message
);
return
SASL_OK
;
}
static
void
jabber_sasl_build_callbacks
(
JabberStream
*
js
)
{
int
id
;
/* Set up our callbacks structure */
if
(
js
->
sasl_cb
==
NULL
)
js
->
sasl_cb
=
g_new0
(
sasl_callback_t
,
6
);
id
=
0
;
js
->
sasl_cb
[
id
].
id
=
SASL_CB_GETREALM
;
js
->
sasl_cb
[
id
].
proc
=
(
void
*
)
jabber_sasl_cb_realm
;
js
->
sasl_cb
[
id
].
context
=
(
void
*
)
js
;
id
++
;
js
->
sasl_cb
[
id
].
id
=
SASL_CB_AUTHNAME
;
js
->
sasl_cb
[
id
].
proc
=
(
void
*
)
jabber_sasl_cb_simple
;
js
->
sasl_cb
[
id
].
context
=
(
void
*
)
js
;
id
++
;
js
->
sasl_cb
[
id
].
id
=
SASL_CB_USER
;
js
->
sasl_cb
[
id
].
proc
=
(
void
*
)
jabber_sasl_cb_simple
;
js
->
sasl_cb
[
id
].
context
=
(
void
*
)
js
;
id
++
;
if
(
js
->
sasl_password
!=
NULL
)
{
js
->
sasl_cb
[
id
].
id
=
SASL_CB_PASS
;
js
->
sasl_cb
[
id
].
proc
=
(
void
*
)
jabber_sasl_cb_secret
;
js
->
sasl_cb
[
id
].
context
=
(
void
*
)
js
;
id
++
;
}
js
->
sasl_cb
[
id
].
id
=
SASL_CB_LOG
;
js
->
sasl_cb
[
id
].
proc
=
(
void
*
)
jabber_sasl_cb_log
;
js
->
sasl_cb
[
id
].
context
=
(
void
*
)
js
;
id
++
;
js
->
sasl_cb
[
id
].
id
=
SASL_CB_LIST_END
;
}
static
JabberSaslState
jabber_cyrus_start
(
JabberStream
*
js
,
PurpleXmlNode
*
mechanisms
,
PurpleXmlNode
**
reply
,
char
**
error
)
{
PurpleXmlNode
*
mechnode
;
JabberSaslState
ret
;
js
->
sasl_mechs
=
g_string_new
(
""
);
js
->
sasl_password
=
g_strdup
(
purple_connection_get_password
(
js
->
gc
));
for
(
mechnode
=
purple_xmlnode_get_child
(
mechanisms
,
"mechanism"
);
mechnode
;
mechnode
=
purple_xmlnode_get_next_twin
(
mechnode
))
{
char
*
mech_name
=
purple_xmlnode_get_data
(
mechnode
);
/* Ignore blank mechanisms and EXTERNAL. External isn't
* supported, and Cyrus SASL's mechanism returns
* SASL_NOMECH when the caller (us) doesn't configure it.
* Except SASL_NOMECH is supposed to mean "no concordant
* mechanisms"... Easiest just to blacklist it (for now).
*/
if
(
!
mech_name
||
!*
mech_name
||
purple_strequal
(
mech_name
,
"EXTERNAL"
))
{
g_free
(
mech_name
);
continue
;
}
g_string_append
(
js
->
sasl_mechs
,
mech_name
);
g_string_append_c
(
js
->
sasl_mechs
,
' '
);
g_free
(
mech_name
);
}
/* Strip off the trailing ' ' */
if
(
js
->
sasl_mechs
->
len
>
1
)
g_string_truncate
(
js
->
sasl_mechs
,
js
->
sasl_mechs
->
len
-
1
);
jabber_sasl_build_callbacks
(
js
);
ret
=
jabber_auth_start_cyrus
(
js
,
reply
,
error
);
/*
* Triggered if no overlap between server and client
* supported mechanisms.
*/
if
(
ret
==
JABBER_SASL_STATE_FAIL
&&
*
error
==
NULL
)
*
error
=
g_strdup
(
_
(
"Server does not use any supported authentication method"
));
return
ret
;
}
static
JabberSaslState
jabber_cyrus_handle_challenge
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
,
PurpleXmlNode
**
reply
,
char
**
error
)
{
char
*
enc_in
=
purple_xmlnode_get_data
(
packet
);
unsigned
char
*
dec_in
;
char
*
enc_out
;
const
char
*
c_out
;
unsigned
int
clen
;
gsize
declen
;
dec_in
=
g_base64_decode
(
enc_in
,
&
declen
);
js
->
sasl_state
=
sasl_client_step
(
js
->
sasl
,
(
char
*
)
dec_in
,
declen
,
NULL
,
&
c_out
,
&
clen
);
g_free
(
enc_in
);
g_free
(
dec_in
);
if
(
js
->
sasl_state
!=
SASL_CONTINUE
&&
js
->
sasl_state
!=
SASL_OK
)
{
gchar
*
tmp
=
g_strdup_printf
(
_
(
"SASL error: %s"
),
sasl_errdetail
(
js
->
sasl
));
purple_debug_error
(
"jabber"
,
"Error is %d : %s
\n
"
,
js
->
sasl_state
,
sasl_errdetail
(
js
->
sasl
));
*
error
=
tmp
;
return
JABBER_SASL_STATE_FAIL
;
}
else
{
PurpleXmlNode
*
response
=
purple_xmlnode_new
(
"response"
);
purple_xmlnode_set_namespace
(
response
,
NS_XMPP_SASL
);
if
(
clen
>
0
)
{
/* Cyrus SASL 2.1.22 appears to contain code to add the charset
* to the response for DIGEST-MD5 but there is no possibility
* it will be executed.
*
* My reading of the digestmd5 plugin indicates the username and
* realm are always encoded in UTF-8 (they seem to be the values
* we pass in), so we need to ensure charset=utf-8 is set.
*/
if
(
!
purple_strequal
(
js
->
current_mech
,
"DIGEST-MD5"
)
||
strstr
(
c_out
,
",charset="
))
/* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */
enc_out
=
g_base64_encode
((
unsigned
char
*
)
c_out
,
clen
);
else
{
char
*
tmp
=
g_strdup_printf
(
"%s,charset=utf-8"
,
c_out
);
enc_out
=
g_base64_encode
((
unsigned
char
*
)
tmp
,
clen
+
14
);
g_free
(
tmp
);
}
purple_xmlnode_insert_data
(
response
,
enc_out
,
-1
);
g_free
(
enc_out
);
}
*
reply
=
response
;
return
JABBER_SASL_STATE_CONTINUE
;
}
}
static
JabberSaslState
jabber_cyrus_handle_success
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
,
char
**
error
)
{
const
void
*
x
;
/* The SASL docs say that if the client hasn't returned OK yet, we
* should try one more round against it
*/
if
(
js
->
sasl_state
!=
SASL_OK
)
{
char
*
enc_in
=
purple_xmlnode_get_data
(
packet
);
unsigned
char
*
dec_in
=
NULL
;
const
char
*
c_out
;
unsigned
int
clen
;
gsize
declen
=
0
;
if
(
enc_in
!=
NULL
)
dec_in
=
g_base64_decode
(
enc_in
,
&
declen
);
js
->
sasl_state
=
sasl_client_step
(
js
->
sasl
,
(
char
*
)
dec_in
,
declen
,
NULL
,
&
c_out
,
&
clen
);
g_free
(
enc_in
);
g_free
(
dec_in
);
if
(
js
->
sasl_state
!=
SASL_OK
)
{
/* This happens when the server sends back jibberish
* in the "additional data with success" case.
* Seen with Wildfire 3.0.1.
*/
*
error
=
g_strdup
(
_
(
"Invalid response from server"
));
return
JABBER_SASL_STATE_FAIL
;
}
}
/* If we've negotiated a security layer, we need to enable it */
if
(
js
->
sasl
)
{
sasl_getprop
(
js
->
sasl
,
SASL_SSF
,
&
x
);
if
(
*
(
int
*
)
x
>
0
)
{
sasl_getprop
(
js
->
sasl
,
SASL_MAXOUTBUF
,
&
x
);
js
->
sasl_maxbuf
=
*
(
int
*
)
x
;
}
}
return
JABBER_SASL_STATE_OK
;
}
static
JabberSaslState
jabber_cyrus_handle_failure
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
,
PurpleXmlNode
**
reply
,
char
**
error
)
{
if
(
js
->
auth_fail_count
++
<
5
)
{
if
(
js
->
current_mech
&&
*
js
->
current_mech
)
{
remove_current_mech
(
js
);
}
/* Should we only try again if we've actually removed a mech? */
if
(
*
js
->
sasl_mechs
->
str
)
{
/* If we have remaining mechs to try, do so */
sasl_dispose
(
&
js
->
sasl
);
return
jabber_auth_start_cyrus
(
js
,
reply
,
error
);
}
else
if
((
js
->
auth_fail_count
==
1
)
&&
purple_strequal
(
js
->
current_mech
,
"GSSAPI"
))
{
/* If we tried GSSAPI first, it failed, and it was the only method we had to try, try jabber:iq:auth
* for compatibility with iChat 10.5 Server and other jabberd based servers.
*
* iChat Server 10.5 and certain other corporate servers offer SASL GSSAPI by default, which is often
* not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails.
*
* Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However,
* I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms.
* Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
* which would connect without issue otherwise. -evands
*/
sasl_dispose
(
&
js
->
sasl
);
js
->
sasl
=
NULL
;
js
->
auth_mech
=
NULL
;
jabber_auth_start_old
(
js
);
return
JABBER_SASL_STATE_CONTINUE
;
}
}
/* Nothing to send */
return
JABBER_SASL_STATE_FAIL
;
}
static
JabberSaslMech
cyrus_mech
=
{
100
,
/* priority */
"*"
,
/* name; Cyrus provides a bunch of mechanisms, so use an invalid
* mechanism name (per rfc4422 3.1). */
jabber_cyrus_start
,
jabber_cyrus_handle_challenge
,
jabber_cyrus_handle_success
,
jabber_cyrus_handle_failure
,
NULL
,
};
JabberSaslMech
*
jabber_auth_get_cyrus_mech
(
void
)
{
return
&
cyrus_mech
;
}