grim/pidgin
Clone
Summary
Browse
Changes
Graph
Fix a meson warning about concatenating lists and strings
12 months ago, Gary Kramlich
10b60bd1cafa
Fix a meson warning about concatenating lists and strings
This is actually allowed in newer versions, but this variable should be a list
anyways.
Testing Done:
Ran `ninja reconfigure` and verified that the warning was gone.
Reviewed at https://reviews.imfreedom.org/r/2493/
/*
* 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
<config.h>
#include
<errno.h>
#include
<glib/gi18n-lib.h>
#include
<gplugin.h>
#include
<gplugin-native.h>
#include
<purple.h>
#include
"auth.h"
#include
"buddy.h"
#include
"caps.h"
#include
"chat.h"
#include
"data.h"
#include
"disco.h"
#include
"ibb.h"
#include
"iq.h"
#include
"jutil.h"
#include
"message.h"
#include
"parser.h"
#include
"presence.h"
#include
"jabber.h"
#include
"roster.h"
#include
"oob.h"
#include
"ping.h"
#include
"si.h"
#include
"xdata.h"
#include
"pep.h"
#include
"adhoccommands.h"
#include
"xmpp.h"
#include
"jingle/jingle.h"
#include
"jingle/content.h"
#include
"jingle/iceudp.h"
#include
"jingle/rawudp.h"
#include
"jingle/rtp.h"
#include
"jingle/session.h"
#define PING_TIMEOUT 60
/* Send a whitespace keepalive to the server if we haven't sent
* anything in the last 120 seconds
*/
#define DEFAULT_INACTIVITY_TIME 120
GList
*
jabber_features
=
NULL
;
GList
*
jabber_identities
=
NULL
;
static
PurpleProtocol
*
xmpp_protocol
=
NULL
;
static
GHashTable
*
jabber_cmds
=
NULL
;
/* PurpleProtocol * => GSList of ids */
static
gint
plugin_ref
=
0
;
static
void
jabber_send_raw
(
PurpleProtocolServer
*
protocol_server
,
JabberStream
*
js
,
const
gchar
*
data
,
gint
len
);
static
void
jabber_remove_feature
(
const
gchar
*
namespace
);
static
gboolean
jabber_initiate_media
(
PurpleProtocolMedia
*
media
,
PurpleAccount
*
account
,
const
char
*
who
,
PurpleMediaSessionType
type
);
static
PurpleMediaCaps
jabber_get_media_caps
(
PurpleProtocolMedia
*
media
,
PurpleAccount
*
account
,
const
char
*
who
);
static
void
jabber_stream_init
(
JabberStream
*
js
)
{
char
*
open_stream
;
g_free
(
js
->
stream_id
);
js
->
stream_id
=
NULL
;
open_stream
=
g_strdup_printf
(
"<stream:stream to='%s' "
"xmlns='"
NS_XMPP_CLIENT
"' "
"xmlns:stream='"
NS_XMPP_STREAMS
"' "
"version='1.0'>"
,
js
->
user
->
domain
);
/* setup the parser fresh for each stream */
jabber_parser_setup
(
js
);
jabber_send_raw
(
NULL
,
js
,
open_stream
,
-1
);
js
->
reinit
=
FALSE
;
g_free
(
open_stream
);
}
static
void
jabber_session_initialized_cb
(
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
from
,
JabberIqType
type
,
G_GNUC_UNUSED
const
char
*
id
,
G_GNUC_UNUSED
PurpleXmlNode
*
packet
,
G_GNUC_UNUSED
gpointer
data
)
{
if
(
type
==
JABBER_IQ_RESULT
)
{
jabber_disco_items_server
(
js
);
}
else
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
(
"Error initializing session"
));
}
}
static
void
jabber_session_init
(
JabberStream
*
js
)
{
JabberIq
*
iq
=
jabber_iq_new
(
js
,
JABBER_IQ_SET
);
PurpleXmlNode
*
session
;
jabber_iq_set_callback
(
iq
,
jabber_session_initialized_cb
,
NULL
);
session
=
purple_xmlnode_new_child
(
iq
->
node
,
"session"
);
purple_xmlnode_set_namespace
(
session
,
NS_XMPP_SESSION
);
jabber_iq_send
(
iq
);
}
static
void
jabber_bind_result_cb
(
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
from
,
JabberIqType
type
,
G_GNUC_UNUSED
const
char
*
id
,
PurpleXmlNode
*
packet
,
G_GNUC_UNUSED
gpointer
data
)
{
PurpleXmlNode
*
bind
;
if
(
type
==
JABBER_IQ_RESULT
&&
(
bind
=
purple_xmlnode_get_child_with_namespace
(
packet
,
"bind"
,
NS_XMPP_BIND
)))
{
PurpleXmlNode
*
jid
;
char
*
full_jid
;
if
((
jid
=
purple_xmlnode_get_child
(
bind
,
"jid"
))
&&
(
full_jid
=
purple_xmlnode_get_data
(
jid
)))
{
jabber_id_free
(
js
->
user
);
js
->
user
=
jabber_id_new
(
full_jid
);
if
(
js
->
user
==
NULL
)
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Invalid response from server"
));
g_free
(
full_jid
);
return
;
}
js
->
user_jb
=
jabber_buddy_find
(
js
,
full_jid
,
TRUE
);
js
->
user_jb
->
subscription
|=
JABBER_SUB_BOTH
;
purple_connection_set_display_name
(
js
->
gc
,
full_jid
);
g_free
(
full_jid
);
}
}
else
{
PurpleConnectionError
reason
=
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
;
char
*
msg
=
jabber_parse_error
(
js
,
packet
,
&
reason
);
purple_connection_error
(
js
->
gc
,
reason
,
msg
);
g_free
(
msg
);
return
;
}
jabber_session_init
(
js
);
}
static
char
*
jabber_prep_resource
(
char
*
input
)
{
const
gchar
*
hostname
=
NULL
,
*
dot
=
NULL
;
gchar
*
result
=
NULL
;
/* Empty resource == don't send any */
if
(
input
==
NULL
||
*
input
==
'\0'
)
return
NULL
;
if
(
strstr
(
input
,
"__HOSTNAME__"
)
==
NULL
)
return
g_strdup
(
input
);
/* Replace __HOSTNAME__ with hostname */
hostname
=
g_get_host_name
();
/* We want only the short hostname, not the FQDN - this will prevent the
* resource string from being unreasonably long on systems which stuff the
* whole FQDN in the hostname */
if
((
dot
=
strchr
(
hostname
,
'.'
))
!=
NULL
)
{
gchar
*
short_hostname
=
g_strndup
(
hostname
,
dot
-
hostname
);
result
=
purple_strreplace
(
input
,
"__HOSTNAME__"
,
short_hostname
);
g_free
(
short_hostname
);
}
else
{
result
=
purple_strreplace
(
input
,
"__HOSTNAME__"
,
hostname
);
}
return
result
;
}
static
gboolean
jabber_process_starttls
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
)
{
PurpleAccount
*
account
=
NULL
;
PurpleXmlNode
*
starttls
=
NULL
;
/* It's a secure BOSH connection, just return FALSE and skip, without doing
* anything extra. XEP-0206 (XMPP Over BOSH): The client SHOULD ignore any
* Transport Layer Security (TLS) feature since BOSH channel encryption
* SHOULD be negotiated at the HTTP layer.
*
* Note: we are already receiving STARTTLS at this point from a SSL/TLS BOSH
* connection, so it is not necessary to check if SSL is supported.
*/
if
(
js
->
bosh
&&
jabber_bosh_connection_is_ssl
(
js
->
bosh
))
{
return
FALSE
;
}
/* Otherwise, it's a standard XMPP connection, or a HTTP (insecure) BOSH connection.
* We request STARTTLS for standard XMPP connections, but we do nothing for insecure
* BOSH connections, per XEP-0206. */
if
(
!
js
->
bosh
)
{
jabber_send_raw
(
NULL
,
js
,
"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"
,
-1
);
return
TRUE
;
}
/* It's an insecure standard XMPP connection, or an insecure BOSH connection, let's
* ignore STARTTLS even it's required by the server to prevent disabling HTTP BOSH
* entirely (sysadmin is responsible to provide HTTPS-only BOSH if security is required),
* and emit errors if encryption is required by the user. */
starttls
=
purple_xmlnode_get_child
(
packet
,
"starttls"
);
if
(
!
js
->
bosh
&&
purple_xmlnode_get_child
(
starttls
,
"required"
))
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
,
_
(
"Server requires TLS/SSL, but no TLS/SSL support was found."
));
return
TRUE
;
}
account
=
purple_connection_get_account
(
js
->
gc
);
if
(
purple_strequal
(
"require_tls"
,
purple_account_get_string
(
account
,
"connection_security"
,
JABBER_DEFAULT_REQUIRE_TLS
)))
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT
,
_
(
"You require encryption, but no TLS/SSL support was found."
));
return
TRUE
;
}
return
FALSE
;
}
void
jabber_stream_features_parse
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
)
{
PurpleAccount
*
account
=
purple_connection_get_account
(
js
->
gc
);
const
char
*
connection_security
=
purple_account_get_string
(
account
,
"connection_security"
,
JABBER_DEFAULT_REQUIRE_TLS
);
if
(
purple_xmlnode_get_child
(
packet
,
"starttls"
))
{
if
(
jabber_process_starttls
(
js
,
packet
))
{
jabber_stream_set_state
(
js
,
JABBER_STREAM_INITIALIZING_ENCRYPTION
);
return
;
}
}
else
if
(
purple_strequal
(
connection_security
,
"require_tls"
)
&&
!
jabber_stream_is_ssl
(
js
))
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
_
(
"You require encryption, but it is not available on this server."
));
return
;
}
if
(
purple_xmlnode_get_child
(
packet
,
"mechanisms"
))
{
jabber_stream_set_state
(
js
,
JABBER_STREAM_AUTHENTICATING
);
jabber_auth_start
(
js
,
packet
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"bind"
))
{
PurpleXmlNode
*
bind
,
*
resource
;
char
*
requested_resource
;
JabberIq
*
iq
=
jabber_iq_new
(
js
,
JABBER_IQ_SET
);
bind
=
purple_xmlnode_new_child
(
iq
->
node
,
"bind"
);
purple_xmlnode_set_namespace
(
bind
,
NS_XMPP_BIND
);
requested_resource
=
jabber_prep_resource
(
js
->
user
->
resource
);
if
(
requested_resource
!=
NULL
)
{
resource
=
purple_xmlnode_new_child
(
bind
,
"resource"
);
purple_xmlnode_insert_data
(
resource
,
requested_resource
,
-1
);
g_free
(
requested_resource
);
}
jabber_iq_set_callback
(
iq
,
jabber_bind_result_cb
,
NULL
);
jabber_iq_send
(
iq
);
}
else
if
(
purple_xmlnode_get_child_with_namespace
(
packet
,
"ver"
,
NS_ROSTER_VERSIONING
))
{
js
->
server_caps
|=
JABBER_CAP_ROSTER_VERSIONING
;
}
else
/* if(purple_xmlnode_get_child_with_namespace(packet, "auth")) */
{
/* If we get an empty stream:features packet, or we explicitly get
* an auth feature with namespace http://jabber.org/features/iq-auth
* we should revert back to iq:auth authentication, even though we're
* connecting to an XMPP server. */
jabber_stream_set_state
(
js
,
JABBER_STREAM_AUTHENTICATING
);
jabber_auth_start_old
(
js
);
}
}
static
void
jabber_stream_handle_error
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
)
{
PurpleConnectionError
reason
=
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
;
char
*
msg
=
jabber_parse_error
(
js
,
packet
,
&
reason
);
purple_connection_error
(
js
->
gc
,
reason
,
msg
);
g_free
(
msg
);
}
static
void
tls_init
(
JabberStream
*
js
);
void
jabber_process_packet
(
JabberStream
*
js
,
PurpleXmlNode
**
packet
)
{
const
char
*
name
;
const
char
*
xmlns
;
purple_signal_emit
(
purple_connection_get_protocol
(
js
->
gc
),
"jabber-receiving-xmlnode"
,
js
->
gc
,
packet
);
/* if the signal leaves us with a null packet, we're done */
if
(
NULL
==
*
packet
)
return
;
name
=
(
*
packet
)
->
name
;
xmlns
=
purple_xmlnode_get_namespace
(
*
packet
);
if
(
purple_strequal
(
name
,
"iq"
))
{
jabber_iq_parse
(
js
,
*
packet
);
}
else
if
(
purple_strequal
(
name
,
"presence"
))
{
jabber_presence_parse
(
js
,
*
packet
);
}
else
if
(
purple_strequal
(
name
,
"message"
))
{
jabber_message_parse
(
js
,
*
packet
);
}
else
if
(
purple_strequal
(
xmlns
,
NS_XMPP_STREAMS
))
{
if
(
purple_strequal
(
name
,
"features"
))
jabber_stream_features_parse
(
js
,
*
packet
);
else
if
(
purple_strequal
(
name
,
"error"
))
jabber_stream_handle_error
(
js
,
*
packet
);
}
else
if
(
purple_strequal
(
xmlns
,
NS_XMPP_SASL
))
{
if
(
js
->
state
!=
JABBER_STREAM_AUTHENTICATING
)
purple_debug_warning
(
"jabber"
,
"Ignoring spurious SASL stanza %s
\n
"
,
name
);
else
{
if
(
purple_strequal
(
name
,
"challenge"
))
jabber_auth_handle_challenge
(
js
,
*
packet
);
else
if
(
purple_strequal
(
name
,
"success"
))
jabber_auth_handle_success
(
js
,
*
packet
);
else
if
(
purple_strequal
(
name
,
"failure"
))
jabber_auth_handle_failure
(
js
,
*
packet
);
}
}
else
if
(
purple_strequal
(
xmlns
,
NS_XMPP_TLS
))
{
if
(
js
->
state
!=
JABBER_STREAM_INITIALIZING_ENCRYPTION
||
G_IS_TLS_CONNECTION
(
js
->
stream
))
{
purple_debug_warning
(
"jabber"
,
"Ignoring spurious %s
\n
"
,
name
);
}
else
{
if
(
purple_strequal
(
name
,
"proceed"
))
tls_init
(
js
);
/* TODO: Handle <failure/>, I guess? */
}
}
else
{
purple_debug_warning
(
"jabber"
,
"Unknown packet: %s
\n
"
,
name
);
}
}
static
void
jabber_push_bytes_cb
(
GObject
*
source
,
GAsyncResult
*
res
,
gpointer
data
)
{
PurpleQueuedOutputStream
*
stream
=
PURPLE_QUEUED_OUTPUT_STREAM
(
source
);
JabberStream
*
js
=
data
;
gboolean
result
;
GError
*
error
=
NULL
;
result
=
purple_queued_output_stream_push_bytes_finish
(
stream
,
res
,
&
error
);
if
(
!
result
)
{
purple_queued_output_stream_clear_queue
(
stream
);
if
(
error
->
code
!=
G_IO_ERROR_CANCELLED
)
{
g_prefix_error
(
&
error
,
"%s"
,
_
(
"Lost connection with server: "
));
purple_connection_take_error
(
js
->
gc
,
error
);
}
else
{
g_error_free
(
error
);
}
}
}
static
gboolean
do_jabber_send_raw
(
JabberStream
*
js
,
const
char
*
data
,
int
len
)
{
GBytes
*
output
;
gboolean
success
=
TRUE
;
g_return_val_if_fail
(
len
>
0
,
FALSE
);
if
(
js
->
state
==
JABBER_STREAM_CONNECTED
)
jabber_stream_restart_inactivity_timer
(
js
);
output
=
g_bytes_new
(
data
,
len
);
purple_queued_output_stream_push_bytes_async
(
js
->
output
,
output
,
G_PRIORITY_DEFAULT
,
js
->
cancellable
,
jabber_push_bytes_cb
,
js
);
g_bytes_unref
(
output
);
return
success
;
}
static
void
jabber_send_raw
(
G_GNUC_UNUSED
PurpleProtocolServer
*
protocol_server
,
JabberStream
*
js
,
const
char
*
data
,
gint
len
)
{
PurpleConnection
*
gc
;
PurpleAccount
*
account
;
gc
=
js
->
gc
;
account
=
purple_connection_get_account
(
gc
);
g_return_if_fail
(
data
!=
NULL
);
/* because printing a tab to debug every minute gets old */
if
(
!
purple_strequal
(
data
,
"
\t
"
))
{
const
char
*
username
;
char
*
text
=
NULL
,
*
last_part
=
NULL
,
*
tag_start
=
NULL
;
/* Because debug logs with plaintext passwords make me sad */
if
(
!
purple_debug_is_unsafe
()
&&
js
->
state
!=
JABBER_STREAM_CONNECTED
&&
/* Either <auth> or <query><password>... */
(((
tag_start
=
strstr
(
data
,
"<auth "
))
&&
strstr
(
data
,
"xmlns='"
NS_XMPP_SASL
"'"
))
||
((
tag_start
=
strstr
(
data
,
"<query "
))
&&
strstr
(
data
,
"xmlns='jabber:iq:auth'>"
)
&&
(
tag_start
=
strstr
(
tag_start
,
"<password>"
)))))
{
char
*
data_start
,
*
tag_end
=
strchr
(
tag_start
,
'>'
);
text
=
g_strdup
(
data
);
/* Better to print out some wacky debugging than crash
* due to a plugin sending bad xml */
if
(
tag_end
==
NULL
)
tag_end
=
tag_start
;
data_start
=
text
+
(
tag_end
-
data
)
+
1
;
last_part
=
strchr
(
data_start
,
'<'
);
*
data_start
=
'\0'
;
}
username
=
purple_connection_get_display_name
(
gc
);
if
(
username
==
NULL
)
{
PurpleContactInfo
*
info
=
PURPLE_CONTACT_INFO
(
account
);
username
=
purple_contact_info_get_username
(
info
);
}
purple_debug_misc
(
"jabber"
,
"Sending%s (%s): %s%s%s
\n
"
,
jabber_stream_is_ssl
(
js
)
?
" (ssl)"
:
""
,
username
,
text
?
text
:
data
,
last_part
?
"password removed"
:
""
,
last_part
?
last_part
:
""
);
g_free
(
text
);
}
purple_signal_emit
(
purple_connection_get_protocol
(
gc
),
"jabber-sending-text"
,
gc
,
&
data
);
if
(
data
==
NULL
)
return
;
if
(
len
==
-1
)
len
=
strlen
(
data
);
if
(
js
->
bosh
)
jabber_bosh_connection_send
(
js
->
bosh
,
data
);
else
do_jabber_send_raw
(
js
,
data
,
len
);
}
static
gint
jabber_protocol_send_raw
(
G_GNUC_UNUSED
PurpleProtocolServer
*
protocol_server
,
PurpleConnection
*
gc
,
const
gchar
*
buf
,
gint
len
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
g_return_val_if_fail
(
js
!=
NULL
,
-1
);
/* TODO: It's probably worthwhile to restrict this to when the account
* state is CONNECTED, but I can /almost/ envision reasons for wanting
* to do things during the connection process.
*/
jabber_send_raw
(
NULL
,
js
,
buf
,
len
);
return
(
len
<
0
?
(
int
)
strlen
(
buf
)
:
len
);
}
static
void
jabber_send_signal_cb
(
PurpleConnection
*
pc
,
PurpleXmlNode
**
packet
,
G_GNUC_UNUSED
gpointer
unused
)
{
JabberStream
*
js
;
char
*
txt
;
int
len
;
if
(
NULL
==
packet
)
return
;
PURPLE_ASSERT_CONNECTION_IS_VALID
(
pc
);
js
=
purple_connection_get_protocol_data
(
pc
);
if
(
NULL
==
js
)
return
;
if
(
js
->
bosh
)
if
(
purple_strequal
((
*
packet
)
->
name
,
"message"
)
||
purple_strequal
((
*
packet
)
->
name
,
"iq"
)
||
purple_strequal
((
*
packet
)
->
name
,
"presence"
))
purple_xmlnode_set_namespace
(
*
packet
,
NS_XMPP_CLIENT
);
txt
=
purple_xmlnode_to_str
(
*
packet
,
&
len
);
jabber_send_raw
(
NULL
,
js
,
txt
,
len
);
g_free
(
txt
);
}
void
jabber_send
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
)
{
purple_signal_emit
(
purple_connection_get_protocol
(
js
->
gc
),
"jabber-sending-xmlnode"
,
js
->
gc
,
&
packet
);
}
static
gboolean
jabber_keepalive_timeout
(
PurpleConnection
*
gc
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Ping timed out"
));
js
->
keepalive_timeout
=
0
;
return
FALSE
;
}
static
void
jabber_keepalive
(
G_GNUC_UNUSED
PurpleProtocolServer
*
protocol_server
,
PurpleConnection
*
gc
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
if
(
js
->
keepalive_timeout
==
0
)
{
jabber_keepalive_ping
(
js
);
js
->
keepalive_timeout
=
g_timeout_add_seconds
(
120
,
G_SOURCE_FUNC
(
jabber_keepalive_timeout
),
gc
);
}
}
static
int
jabber_get_keepalive_interval
(
G_GNUC_UNUSED
PurpleProtocolServer
*
protocol_server
)
{
return
PING_TIMEOUT
;
}
static
gboolean
jabber_recv_cb
(
GObject
*
stream
,
gpointer
data
)
{
PurpleConnection
*
gc
=
data
;
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
gssize
len
;
gchar
buf
[
4096
];
GError
*
error
=
NULL
;
PURPLE_ASSERT_CONNECTION_IS_VALID
(
gc
);
do
{
len
=
g_pollable_input_stream_read_nonblocking
(
G_POLLABLE_INPUT_STREAM
(
stream
),
buf
,
sizeof
(
buf
)
-
1
,
js
->
cancellable
,
&
error
);
if
(
len
==
0
)
{
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Server closed the connection"
));
js
->
inpa
=
0
;
return
G_SOURCE_REMOVE
;
}
else
if
(
len
<
0
)
{
if
(
error
->
code
==
G_IO_ERROR_WOULD_BLOCK
)
{
g_error_free
(
error
);
return
G_SOURCE_CONTINUE
;
}
else
if
(
error
->
code
==
G_IO_ERROR_CANCELLED
)
{
g_error_free
(
error
);
}
else
{
g_prefix_error
(
&
error
,
"%s"
,
_
(
"Lost connection with server: "
));
purple_connection_take_error
(
js
->
gc
,
error
);
}
js
->
inpa
=
0
;
return
G_SOURCE_REMOVE
;
}
purple_connection_update_last_received
(
gc
);
buf
[
len
]
=
'\0'
;
purple_debug_misc
(
"jabber"
,
"Recv (%"
G_GSSIZE_FORMAT
"): %s"
,
len
,
buf
);
jabber_parser_process
(
js
,
buf
,
len
);
if
(
js
->
reinit
)
jabber_stream_init
(
js
);
}
while
(
len
>
0
);
return
G_SOURCE_CONTINUE
;
}
static
void
jabber_stream_connect_finish
(
JabberStream
*
js
,
GIOStream
*
stream
)
{
GSource
*
source
;
js
->
stream
=
stream
;
js
->
input
=
g_object_ref
(
g_io_stream_get_input_stream
(
js
->
stream
));
js
->
output
=
purple_queued_output_stream_new
(
g_io_stream_get_output_stream
(
js
->
stream
));
if
(
js
->
state
==
JABBER_STREAM_CONNECTING
)
{
jabber_send_raw
(
NULL
,
js
,
"<?xml version='1.0' ?>"
,
-1
);
}
jabber_stream_set_state
(
js
,
JABBER_STREAM_INITIALIZING
);
source
=
g_pollable_input_stream_create_source
(
G_POLLABLE_INPUT_STREAM
(
js
->
input
),
js
->
cancellable
);
g_source_set_callback
(
source
,
G_SOURCE_FUNC
(
jabber_recv_cb
),
js
->
gc
,
NULL
);
js
->
inpa
=
g_source_attach
(
source
,
NULL
);
g_source_unref
(
source
);
}
static
void
jabber_login_callback
(
GObject
*
source_object
,
GAsyncResult
*
res
,
gpointer
data
)
{
GSocketClient
*
client
=
G_SOCKET_CLIENT
(
source_object
);
JabberStream
*
js
=
data
;
GSocketConnection
*
conn
;
GIOStream
*
stream
;
gboolean
is_old_ssl
=
g_socket_client_get_tls
(
client
);
GError
*
error
=
NULL
;
conn
=
g_socket_client_connect_to_host_finish
(
client
,
res
,
&
error
);
if
(
conn
==
NULL
)
{
if
(
g_error_matches
(
error
,
G_IO_ERROR
,
G_IO_ERROR_CANCELLED
))
{
g_error_free
(
error
);
return
;
}
else
if
(
is_old_ssl
)
{
/* Old-style SSL only makes a direct connection, or fails. */
purple_connection_take_error
(
js
->
gc
,
error
);
return
;
}
g_error_free
(
error
);
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Unable to connect"
));
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Unable to connect"
));
return
;
}
if
(
is_old_ssl
)
{
stream
=
G_IO_STREAM
(
g_tcp_wrapper_connection_get_base_io_stream
(
G_TCP_WRAPPER_CONNECTION
(
conn
)));
}
else
{
stream
=
G_IO_STREAM
(
conn
);
}
jabber_stream_connect_finish
(
js
,
stream
);
if
(
is_old_ssl
)
{
/* Tell the app that we're doing encryption */
jabber_stream_set_state
(
js
,
JABBER_STREAM_INITIALIZING_ENCRYPTION
);
}
}
static
void
tls_handshake_cb
(
GObject
*
source_object
,
GAsyncResult
*
res
,
gpointer
data
)
{
JabberStream
*
js
=
data
;
GError
*
error
=
NULL
;
if
(
!
g_tls_connection_handshake_finish
(
G_TLS_CONNECTION
(
source_object
),
res
,
&
error
))
{
if
(
g_error_matches
(
error
,
G_IO_ERROR
,
G_IO_ERROR_CANCELLED
))
{
/* Connection already closed/freed. Escape. */
}
else
if
(
g_error_matches
(
error
,
G_TLS_ERROR
,
G_TLS_ERROR_HANDSHAKE
))
{
/* In Gio, a handshake error is because of the cert */
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR
,
_
(
"SSL peer presented an invalid certificate"
));
}
else
{
/* Report any other errors as handshake failing */
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR
,
_
(
"SSL Handshake Failed"
));
}
g_error_free
(
error
);
return
;
}
jabber_stream_connect_finish
(
js
,
js
->
stream
);
/* Tell the app that we're doing encryption */
jabber_stream_set_state
(
js
,
JABBER_STREAM_INITIALIZING_ENCRYPTION
);
}
static
void
tls_init
(
JabberStream
*
js
)
{
GSocketConnectable
*
identity
;
GIOStream
*
tls_conn
;
GError
*
error
=
NULL
;
g_clear_handle_id
(
&
js
->
inpa
,
g_source_remove
);
js
->
input
=
NULL
;
g_filter_output_stream_set_close_base_stream
(
G_FILTER_OUTPUT_STREAM
(
js
->
output
),
FALSE
);
g_output_stream_close
(
G_OUTPUT_STREAM
(
js
->
output
),
js
->
cancellable
,
NULL
);
js
->
output
=
NULL
;
identity
=
g_network_address_new
(
js
->
certificate_CN
,
0
);
tls_conn
=
g_tls_client_connection_new
(
js
->
stream
,
identity
,
&
error
);
g_object_unref
(
identity
);
if
(
tls_conn
==
NULL
)
{
purple_debug_warning
(
"jabber"
,
"Error creating TLS client connection: %s"
,
error
->
message
);
g_clear_error
(
&
error
);
purple_connection_error
(
js
->
gc
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"SSL Connection Failed"
));
return
;
}
g_clear_object
(
&
js
->
stream
);
js
->
stream
=
G_IO_STREAM
(
tls_conn
);
g_tls_connection_handshake_async
(
G_TLS_CONNECTION
(
tls_conn
),
G_PRIORITY_DEFAULT
,
js
->
cancellable
,
tls_handshake_cb
,
js
);
}
static
void
srv_resolved_cb
(
GObject
*
source_object
,
GAsyncResult
*
result
,
gpointer
data
)
{
GSocketClient
*
client
=
G_SOCKET_CLIENT
(
source_object
);
JabberStream
*
js
=
data
;
GSocketConnection
*
conn
;
GError
*
error
=
NULL
;
conn
=
g_socket_client_connect_to_service_finish
(
client
,
result
,
&
error
);
if
(
error
)
{
if
(
g_error_matches
(
error
,
G_IO_ERROR
,
G_IO_ERROR_CANCELLED
))
{
/* Do nothing; cancelled. */
}
else
if
(
g_error_matches
(
error
,
G_RESOLVER_ERROR
,
G_RESOLVER_ERROR_NOT_FOUND
))
{
/* If there was no response, then attempt fallback behaviour of XMPP
* Core 3.2.2. */
purple_debug_warning
(
"jabber"
,
"SRV lookup failed, proceeding with normal connection : %s"
,
error
->
message
);
g_socket_client_connect_to_host_async
(
js
->
client
,
js
->
user
->
domain
,
purple_account_get_int
(
purple_connection_get_account
(
js
->
gc
),
"port"
,
5222
),
js
->
cancellable
,
jabber_login_callback
,
js
);
}
else
{
/* If resolving failed or connecting failed, then just error out, as
* in XMPP Core 3.2.1 step 8. */
purple_connection_g_error
(
js
->
gc
,
error
);
}
g_error_free
(
error
);
return
;
}
jabber_stream_connect_finish
(
js
,
G_IO_STREAM
(
conn
));
}
static
JabberStream
*
jabber_stream_new
(
PurpleAccount
*
account
)
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
PurpleContactInfo
*
info
=
PURPLE_CONTACT_INFO
(
account
);
GProxyResolver
*
resolver
;
GError
*
error
=
NULL
;
JabberStream
*
js
;
PurplePresence
*
presence
;
gchar
*
user
;
gchar
*
slash
;
resolver
=
purple_proxy_get_proxy_resolver
(
account
,
&
error
);
if
(
resolver
==
NULL
)
{
purple_debug_error
(
"jabber"
,
"Unable to get account proxy resolver: %s"
,
error
->
message
);
g_error_free
(
error
);
return
NULL
;
}
js
=
g_new0
(
JabberStream
,
1
);
purple_connection_set_protocol_data
(
gc
,
js
);
js
->
gc
=
gc
;
js
->
http_conns
=
soup_session_new_with_options
(
"proxy-resolver"
,
resolver
,
NULL
);
g_object_unref
(
resolver
);
/* we might want to expose this at some point */
js
->
cancellable
=
g_cancellable_new
();
user
=
g_strdup
(
purple_contact_info_get_username
(
info
));
/* jabber_id_new doesn't accept "user@domain/" as valid */
slash
=
strchr
(
user
,
'/'
);
if
(
slash
&&
*
(
slash
+
1
)
==
'\0'
)
*
slash
=
'\0'
;
js
->
user
=
jabber_id_new
(
user
);
if
(
!
js
->
user
)
{
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
_
(
"Invalid XMPP ID"
));
g_free
(
user
);
/* Destroying the connection will free the JabberStream */
return
NULL
;
}
if
(
!
js
->
user
->
node
||
*
(
js
->
user
->
node
)
==
'\0'
)
{
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
_
(
"Invalid XMPP ID. Username portion must be set."
));
g_free
(
user
);
/* Destroying the connection will free the JabberStream */
return
NULL
;
}
if
(
!
js
->
user
->
domain
||
*
(
js
->
user
->
domain
)
==
'\0'
)
{
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
_
(
"Invalid XMPP ID. Domain must be set."
));
g_free
(
user
);
/* Destroying the connection will free the JabberStream */
return
NULL
;
}
js
->
buddies
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
jabber_buddy_free
);
/* This is overridden during binding, but we need it here
* in case the server only does legacy non-sasl auth!.
*/
purple_connection_set_display_name
(
gc
,
user
);
js
->
user_jb
=
jabber_buddy_find
(
js
,
user
,
TRUE
);
g_free
(
user
);
if
(
!
js
->
user_jb
)
{
/* This basically *can't* fail, but for good measure... */
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
_
(
"Invalid XMPP ID"
));
/* Destroying the connection will free the JabberStream */
g_return_val_if_reached
(
NULL
);
}
js
->
user_jb
->
subscription
|=
JABBER_SUB_BOTH
;
js
->
iq_callbacks
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
jabber_iq_callbackdata_free
);
js
->
chats
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
g_free
,
(
GDestroyNotify
)
jabber_chat_free
);
js
->
next_id
=
g_random_int
();
js
->
keepalive_timeout
=
0
;
js
->
max_inactivity
=
DEFAULT_INACTIVITY_TIME
;
/* Set the default protocol version to 1.0. Overridden in parser.c. */
js
->
protocol_version
.
major
=
1
;
js
->
protocol_version
.
minor
=
0
;
js
->
sessions
=
NULL
;
/* if we are idle, set idle-ness on the stream (this could happen if we get
disconnected and the reconnects while being idle. I don't think it makes
sense to do this when registering a new account... */
presence
=
purple_account_get_presence
(
account
);
if
(
purple_presence_is_idle
(
presence
))
{
GDateTime
*
idle
=
purple_presence_get_idle_time
(
presence
);
js
->
idle
=
0
;
if
(
idle
!=
NULL
)
{
js
->
idle
=
g_date_time_to_unix
(
idle
);
}
}
return
js
;
}
static
void
jabber_stream_connect
(
JabberStream
*
js
)
{
PurpleConnection
*
gc
=
js
->
gc
;
PurpleAccount
*
account
=
purple_connection_get_account
(
gc
);
const
char
*
connect_server
=
purple_account_get_string
(
account
,
"connect_server"
,
""
);
const
char
*
bosh_url
=
purple_account_get_string
(
account
,
"bosh_url"
,
""
);
GError
*
error
=
NULL
;
jabber_stream_set_state
(
js
,
JABBER_STREAM_CONNECTING
);
/* If both BOSH and a Connect Server are specified, we prefer BOSH. I'm not
* attached to that choice, though.
*/
if
(
*
bosh_url
)
{
js
->
bosh
=
jabber_bosh_connection_new
(
js
,
bosh_url
);
if
(
!
js
->
bosh
)
{
purple_connection_error
(
gc
,
PURPLE_CONNECTION_ERROR_INVALID_SETTINGS
,
_
(
"Malformed BOSH URL"
));
}
return
;
}
js
->
client
=
purple_gio_socket_client_new
(
account
,
&
error
);
if
(
js
->
client
==
NULL
)
{
purple_connection_take_error
(
gc
,
error
);
return
;
}
js
->
certificate_CN
=
g_strdup
(
connect_server
[
0
]
?
connect_server
:
js
->
user
->
domain
);
/* if they've got old-ssl mode going, we probably want to ignore SRV lookups */
if
(
purple_strequal
(
"old_ssl"
,
purple_account_get_string
(
account
,
"connection_security"
,
JABBER_DEFAULT_REQUIRE_TLS
)))
{
g_socket_client_set_tls
(
js
->
client
,
TRUE
);
g_socket_client_connect_to_host_async
(
js
->
client
,
js
->
certificate_CN
,
purple_account_get_int
(
account
,
"port"
,
5223
),
js
->
cancellable
,
jabber_login_callback
,
js
);
return
;
}
/* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
* invoke the magic of SRV lookups, to figure out host and port */
if
(
connect_server
[
0
])
{
g_socket_client_connect_to_host_async
(
js
->
client
,
connect_server
,
purple_account_get_int
(
account
,
"port"
,
5222
),
js
->
cancellable
,
jabber_login_callback
,
js
);
}
else
{
g_socket_client_connect_to_service_async
(
js
->
client
,
js
->
user
->
domain
,
"xmpp-client"
,
js
->
cancellable
,
srv_resolved_cb
,
js
);
}
}
static
void
jabber_login
(
G_GNUC_UNUSED
PurpleProtocol
*
protocol
,
PurpleAccount
*
account
)
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
JabberStream
*
js
;
PurpleImage
*
image
;
purple_connection_set_flags
(
gc
,
PURPLE_CONNECTION_FLAG_HTML
|
PURPLE_CONNECTION_FLAG_NO_IMAGES
);
js
=
jabber_stream_new
(
account
);
if
(
js
==
NULL
)
return
;
/* replace old default proxies with the new default: NULL
* TODO: these can eventually be removed */
if
(
purple_strequal
(
"proxy.jabber.org"
,
purple_account_get_string
(
account
,
"ft_proxies"
,
""
))
||
purple_strequal
(
"proxy.eu.jabber.org"
,
purple_account_get_string
(
account
,
"ft_proxies"
,
""
)))
purple_account_set_string
(
account
,
"ft_proxies"
,
NULL
);
/*
* Calculate the avatar hash for our current image so we know (when we
* fetch our vCard and PEP avatar) if we should send our avatar to the
* server.
*/
image
=
purple_buddy_icons_find_account_icon
(
account
);
if
(
image
!=
NULL
)
{
js
->
initial_avatar_hash
=
g_compute_checksum_for_data
(
G_CHECKSUM_SHA1
,
purple_image_get_data
(
image
),
purple_image_get_data_size
(
image
)
);
g_object_unref
(
image
);
}
jabber_stream_connect
(
js
);
}
/* TODO: As Will pointed out in IRC, after being notified by the core to
* shutdown, we should async. wait for the server to send us the stream
* termination before destroying everything. That seems like it would require
* changing the semantics of protocol's close(), so it's a good idea for 3.0.0.
*/
static
void
jabber_close
(
G_GNUC_UNUSED
PurpleProtocol
*
protocol
,
PurpleConnection
*
gc
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
/* Close all of the open Jingle sessions on this stream */
jingle_terminate_sessions
(
js
);
if
(
js
->
bosh
)
{
jabber_bosh_connection_destroy
(
js
->
bosh
);
js
->
bosh
=
NULL
;
}
else
if
(
js
->
output
!=
NULL
)
{
/* We should emit the stream termination message here
* normally, but since we destroy the jabber stream just
* after, it has no way to effectively go out on the
* wire. Moreover, it causes a connection lost error in
* the output queued stream that triggers an
* heap-use-after-free error in jabber_push_bytes_cb().
*
* This case happens when disabling the jabber account
* from the dialog box.
*
* jabber_send_raw(js, "</stream:stream>", -1);
*/
g_clear_handle_id
(
&
js
->
inpa
,
g_source_remove
);
purple_gio_graceful_close
(
js
->
stream
,
js
->
input
,
G_OUTPUT_STREAM
(
js
->
output
));
}
g_clear_object
(
&
js
->
output
);
g_clear_object
(
&
js
->
input
);
g_clear_object
(
&
js
->
stream
);
jabber_buddy_remove_all_pending_buddy_info_requests
(
js
);
jabber_parser_free
(
js
);
g_clear_pointer
(
&
js
->
iq_callbacks
,
g_hash_table_destroy
);
g_clear_pointer
(
&
js
->
buddies
,
g_hash_table_destroy
);
g_clear_pointer
(
&
js
->
chats
,
g_hash_table_destroy
);
g_list_free_full
(
js
->
chat_servers
,
g_free
);
g_list_free_full
(
js
->
bs_proxies
,
(
GDestroyNotify
)
jabber_bytestreams_streamhost_free
);
if
(
js
->
http_conns
)
{
soup_session_abort
(
js
->
http_conns
);
g_object_unref
(
js
->
http_conns
);
}
g_free
(
js
->
stream_id
);
g_clear_pointer
(
&
js
->
user
,
jabber_id_free
);
g_free
(
js
->
initial_avatar_hash
);
g_free
(
js
->
avatar_hash
);
g_free
(
js
->
caps_hash
);
if
(
js
->
auth_mech
&&
js
->
auth_mech
->
dispose
)
js
->
auth_mech
->
dispose
(
js
);
g_free
(
js
->
serverFQDN
);
g_list_free_full
(
js
->
commands
,
(
GDestroyNotify
)
jabber_adhoc_commands_free
);
g_free
(
js
->
server_name
);
g_free
(
js
->
certificate_CN
);
g_free
(
js
->
old_msg
);
g_free
(
js
->
old_avatarhash
);
g_clear_handle_id
(
&
js
->
keepalive_timeout
,
g_source_remove
);
g_clear_handle_id
(
&
js
->
inactivity_timer
,
g_source_remove
);
g_clear_handle_id
(
&
js
->
conn_close_timeout
,
g_source_remove
);
g_cancellable_cancel
(
js
->
cancellable
);
g_object_unref
(
G_OBJECT
(
js
->
cancellable
));
g_free
(
js
);
purple_connection_set_protocol_data
(
gc
,
NULL
);
}
void
jabber_stream_set_state
(
JabberStream
*
js
,
JabberStreamState
state
)
{
js
->
state
=
state
;
if
(
state
==
JABBER_STREAM_INITIALIZING
)
{
jabber_stream_init
(
js
);
}
else
if
(
state
==
JABBER_STREAM_CONNECTED
)
{
/* Send initial presence */
jabber_presence_send
(
js
,
TRUE
);
/* Start up the inactivity timer */
jabber_stream_restart_inactivity_timer
(
js
);
purple_connection_set_state
(
js
->
gc
,
PURPLE_CONNECTION_STATE_CONNECTED
);
}
}
char
*
jabber_get_next_id
(
JabberStream
*
js
)
{
return
g_strdup_printf
(
"purple%x"
,
js
->
next_id
++
);
}
static
void
jabber_idle_set
(
G_GNUC_UNUSED
PurpleProtocolServer
*
protocol_server
,
PurpleConnection
*
gc
,
gint
idle
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
js
->
idle
=
idle
?
time
(
NULL
)
-
idle
:
idle
;
/* send out an updated prescence */
purple_debug_info
(
"jabber"
,
"sending updated presence for idle
\n
"
);
jabber_presence_send
(
js
,
FALSE
);
}
void
jabber_blocklist_parse_push
(
G_GNUC_UNUSED
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
from
,
G_GNUC_UNUSED
JabberIqType
type
,
G_GNUC_UNUSED
const
char
*
id
,
G_GNUC_UNUSED
PurpleXmlNode
*
child
)
{
#if 0
JabberIq *result;
PurpleXmlNode *item;
PurpleAccount *account;
gboolean is_block;
GSList *deny;
if (!jabber_is_own_account(js, from)) {
PurpleXmlNode *error, *x;
result = jabber_iq_new(js, JABBER_IQ_ERROR);
purple_xmlnode_set_attrib(result->node, "id", id);
if (from)
purple_xmlnode_set_attrib(result->node, "to", from);
error = purple_xmlnode_new_child(result->node, "error");
purple_xmlnode_set_attrib(error, "type", "cancel");
x = purple_xmlnode_new_child(error, "not-allowed");
purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS);
jabber_iq_send(result);
return;
}
account = purple_connection_get_account(js->gc);
is_block = purple_strequal(child->name, "block");
item = purple_xmlnode_get_child(child, "item");
if (!is_block && item == NULL) {
/* Unblock everyone */
purple_debug_info("jabber", "Received unblock push. Unblocking everyone.\n");
while ((deny = purple_account_privacy_get_denied(account)) != NULL) {
purple_account_privacy_deny_remove(account, deny->data, TRUE);
}
} else if (item == NULL) {
/* An empty <block/> is bogus */
PurpleXmlNode *error, *x;
result = jabber_iq_new(js, JABBER_IQ_ERROR);
purple_xmlnode_set_attrib(result->node, "id", id);
error = purple_xmlnode_new_child(result->node, "error");
purple_xmlnode_set_attrib(error, "type", "modify");
x = purple_xmlnode_new_child(error, "bad-request");
purple_xmlnode_set_namespace(x, NS_XMPP_STANZAS);
jabber_iq_send(result);
return;
} else {
for ( ; item; item = purple_xmlnode_get_next_twin(item)) {
const char *jid = purple_xmlnode_get_attrib(item, "jid");
if (jid == NULL || *jid == '\0')
continue;
if (is_block)
purple_account_privacy_deny_add(account, jid, TRUE);
else
purple_account_privacy_deny_remove(account, jid, TRUE);
}
}
result = jabber_iq_new(js, JABBER_IQ_RESULT);
purple_xmlnode_set_attrib(result->node, "id", id);
jabber_iq_send(result);
#endif
}
#if 0
static void jabber_blocklist_parse(JabberStream *js, const char *from,
JabberIqType type, const char *id,
PurpleXmlNode *packet, gpointer data)
{
PurpleXmlNode *blocklist, *item;
PurpleAccount *account;
GSList *deny;
blocklist = purple_xmlnode_get_child_with_namespace(packet,
"blocklist", NS_SIMPLE_BLOCKING);
account = purple_connection_get_account(js->gc);
if (type == JABBER_IQ_ERROR || blocklist == NULL)
return;
/* This is the only privacy method supported by XEP-0191 */
purple_account_set_privacy_type(account, PURPLE_ACCOUNT_PRIVACY_DENY_USERS);
/*
* TODO: When account->deny is something more than a hash table, this can
* be re-written to find the set intersection and difference.
*/
while ((deny = purple_account_privacy_get_denied(account)))
purple_account_privacy_deny_remove(account, deny->data, TRUE);
item = purple_xmlnode_get_child(blocklist, "item");
while (item != NULL) {
const char *jid = purple_xmlnode_get_attrib(item, "jid");
purple_account_privacy_deny_add(account, jid, TRUE);
item = purple_xmlnode_get_next_twin(item);
}
}
#endif
void
jabber_request_block_list
(
G_GNUC_UNUSED
JabberStream
*
js
)
{
#if 0
JabberIq *iq;
PurpleXmlNode *blocklist;
iq = jabber_iq_new(js, JABBER_IQ_GET);
blocklist = purple_xmlnode_new_child(iq->node, "blocklist");
purple_xmlnode_set_namespace(blocklist, NS_SIMPLE_BLOCKING);
jabber_iq_set_callback(iq, jabber_blocklist_parse, NULL);
jabber_iq_send(iq);
#endif
}
#if 0
static void
jabber_add_deny(PurpleProtocolPrivacy *privacy, PurpleConnection *gc,
const char *who)
{
JabberStream *js;
JabberIq *iq;
PurpleXmlNode *block, *item;
g_return_if_fail(who != NULL && *who != '\0');
js = purple_connection_get_protocol_data(gc);
if (js == NULL)
return;
if (!(js->server_caps & JABBER_CAP_BLOCKING))
{
purple_notify_error(NULL, _("Server doesn't support blocking"),
_("Server doesn't support blocking"), NULL,
purple_request_cpar_from_connection(gc));
return;
}
iq = jabber_iq_new(js, JABBER_IQ_SET);
block = purple_xmlnode_new_child(iq->node, "block");
purple_xmlnode_set_namespace(block, NS_SIMPLE_BLOCKING);
item = purple_xmlnode_new_child(block, "item");
purple_xmlnode_set_attrib(item, "jid", who);
jabber_iq_send(iq);
}
static void
jabber_remove_deny(PurpleProtocolPrivacy *privacy, PurpleConnection *gc,
const char *who)
{
JabberStream *js;
JabberIq *iq;
PurpleXmlNode *unblock, *item;
g_return_if_fail(who != NULL && *who != '\0');
js = purple_connection_get_protocol_data(gc);
if (js == NULL)
return;
if (!(js->server_caps & JABBER_CAP_BLOCKING))
return;
iq = jabber_iq_new(js, JABBER_IQ_SET);
unblock = purple_xmlnode_new_child(iq->node, "unblock");
purple_xmlnode_set_namespace(unblock, NS_SIMPLE_BLOCKING);
item = purple_xmlnode_new_child(unblock, "item");
purple_xmlnode_set_attrib(item, "jid", who);
jabber_iq_send(iq);
}
#endif
void
jabber_add_feature
(
const
char
*
namespace
,
JabberFeatureEnabled
cb
)
{
JabberFeature
*
feat
;
g_return_if_fail
(
namespace
!=
NULL
);
feat
=
g_new0
(
JabberFeature
,
1
);
feat
->
namespace
=
g_strdup
(
namespace
);
feat
->
is_enabled
=
cb
;
/* try to remove just in case it already exists in the list */
jabber_remove_feature
(
namespace
);
jabber_features
=
g_list_append
(
jabber_features
,
feat
);
}
static
void
jabber_feature_free
(
JabberFeature
*
feature
)
{
g_return_if_fail
(
feature
!=
NULL
);
g_free
(
feature
->
namespace
);
g_free
(
feature
);
}
static
void
jabber_remove_feature
(
const
char
*
namespace
)
{
GList
*
feature
;
for
(
feature
=
jabber_features
;
feature
;
feature
=
feature
->
next
)
{
JabberFeature
*
feat
=
(
JabberFeature
*
)
feature
->
data
;
if
(
purple_strequal
(
feat
->
namespace
,
namespace
))
{
jabber_feature_free
(
feat
);
jabber_features
=
g_list_delete_link
(
jabber_features
,
feature
);
break
;
}
}
}
gint
jabber_identity_compare
(
gconstpointer
a
,
gconstpointer
b
)
{
const
JabberIdentity
*
ac
;
const
JabberIdentity
*
bc
;
gint
cat_cmp
;
gint
typ_cmp
;
ac
=
a
;
bc
=
b
;
cat_cmp
=
g_strcmp0
(
ac
->
category
,
bc
->
category
);
if
(
cat_cmp
!=
0
)
{
return
cat_cmp
;
}
typ_cmp
=
g_strcmp0
(
ac
->
type
,
bc
->
type
);
if
(
typ_cmp
!=
0
)
{
return
typ_cmp
;
}
return
g_strcmp0
(
ac
->
lang
,
bc
->
lang
);
}
JabberIdentity
*
jabber_identity_new
(
const
gchar
*
category
,
const
gchar
*
type
,
const
gchar
*
lang
,
const
gchar
*
name
)
{
JabberIdentity
*
id
=
g_new0
(
JabberIdentity
,
1
);
id
->
category
=
g_strdup
(
category
);
id
->
type
=
g_strdup
(
type
);
id
->
lang
=
g_strdup
(
lang
);
id
->
name
=
g_strdup
(
name
);
return
id
;
}
void
jabber_identity_free
(
JabberIdentity
*
id
)
{
g_return_if_fail
(
id
!=
NULL
);
g_free
(
id
->
category
);
g_free
(
id
->
type
);
g_free
(
id
->
lang
);
g_free
(
id
->
name
);
g_free
(
id
);
}
/*
* jabber_add_identity:
* @category: The category of the identity.
* @type: The type of the identity.
* @language: (nullable): The language localization of the name.
* @name: The name of the identity.
*
* Adds an identity to this jabber library instance. For list of valid values
* visit the website of the XMPP Registrar
* (http://xmpp.org/registrar/disco-categories.html#client)
*
* Like with jabber_add_feature, if you call this while accounts are connected,
* Bad Things will happen.
*/
static
void
jabber_add_identity
(
const
gchar
*
category
,
const
gchar
*
type
,
const
gchar
*
lang
,
const
gchar
*
name
)
{
GList
*
identity
;
JabberIdentity
*
ident
;
/* both required according to XEP-0030 */
g_return_if_fail
(
category
!=
NULL
);
g_return_if_fail
(
type
!=
NULL
);
ident
=
jabber_identity_new
(
category
,
type
,
lang
,
name
);
/* Check if this identity is already there... */
identity
=
g_list_find_custom
(
jabber_identities
,
ident
,
jabber_identity_compare
);
if
(
identity
!=
NULL
)
{
jabber_identity_free
(
ident
);
return
;
}
jabber_identities
=
g_list_insert_sorted
(
jabber_identities
,
ident
,
jabber_identity_compare
);
}
void
jabber_bytestreams_streamhost_free
(
JabberBytestreamsStreamhost
*
sh
)
{
g_return_if_fail
(
sh
!=
NULL
);
g_free
(
sh
->
jid
);
g_free
(
sh
->
host
);
g_free
(
sh
->
zeroconf
);
g_free
(
sh
);
}
gboolean
jabber_stream_is_ssl
(
JabberStream
*
js
)
{
return
(
js
->
bosh
&&
jabber_bosh_connection_is_ssl
(
js
->
bosh
))
||
(
!
js
->
bosh
&&
G_IS_TLS_CONNECTION
(
js
->
stream
));
}
static
gboolean
inactivity_cb
(
gpointer
data
)
{
JabberStream
*
js
=
data
;
/* We want whatever is sent to set this. It's okay because
* the eventloop unsets it via the return FALSE.
*/
js
->
inactivity_timer
=
0
;
if
(
js
->
bosh
)
{
jabber_bosh_connection_send_keepalive
(
js
->
bosh
);
}
else
{
jabber_send_raw
(
NULL
,
js
,
"
\t
"
,
1
);
}
return
FALSE
;
}
void
jabber_stream_restart_inactivity_timer
(
JabberStream
*
js
)
{
g_clear_handle_id
(
&
js
->
inactivity_timer
,
g_source_remove
);
g_return_if_fail
(
js
->
max_inactivity
>
0
);
js
->
inactivity_timer
=
g_timeout_add_seconds
(
js
->
max_inactivity
,
inactivity_cb
,
js
);
}
static
const
char
*
jabber_list_emblem
(
G_GNUC_UNUSED
PurpleProtocolClient
*
client
,
PurpleBuddy
*
b
)
{
JabberStream
*
js
;
JabberBuddy
*
jb
=
NULL
;
PurpleConnection
*
gc
=
purple_account_get_connection
(
purple_buddy_get_account
(
b
));
if
(
!
gc
)
return
NULL
;
js
=
purple_connection_get_protocol_data
(
gc
);
if
(
js
)
jb
=
jabber_buddy_find
(
js
,
purple_buddy_get_name
(
b
),
FALSE
);
if
(
!
PURPLE_BUDDY_IS_ONLINE
(
b
))
{
if
(
jb
&&
(
jb
->
subscription
&
JABBER_SUB_PENDING
||
!
(
jb
->
subscription
&
JABBER_SUB_TO
)))
return
"not-authorized"
;
}
if
(
jb
)
{
JabberBuddyResource
*
jbr
=
jabber_buddy_find_resource
(
jb
,
NULL
);
if
(
jbr
)
{
const
gchar
*
client_type
=
jabber_resource_get_identity_category_type
(
jbr
,
"client"
);
if
(
client_type
)
{
if
(
purple_strequal
(
client_type
,
"phone"
))
{
return
"mobile"
;
}
else
if
(
purple_strequal
(
client_type
,
"web"
))
{
return
"external"
;
}
else
if
(
purple_strequal
(
client_type
,
"handheld"
))
{
return
"hiptop"
;
}
else
if
(
purple_strequal
(
client_type
,
"bot"
))
{
return
"bot"
;
}
/* the default value "pc" falls through and has no emblem */
}
}
}
return
NULL
;
}
static
GList
*
jabber_status_types
(
G_GNUC_UNUSED
PurpleProtocol
*
protocol
,
G_GNUC_UNUSED
PurpleAccount
*
account
)
{
PurpleStatusType
*
type
;
GList
*
types
=
NULL
;
GValue
*
priority_value
;
GValue
*
buzz_enabled
;
priority_value
=
purple_value_new
(
G_TYPE_INT
);
g_value_set_int
(
priority_value
,
1
);
buzz_enabled
=
purple_value_new
(
G_TYPE_BOOLEAN
);
g_value_set_boolean
(
buzz_enabled
,
TRUE
);
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_AVAILABLE
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_ONLINE
),
NULL
,
TRUE
,
TRUE
,
FALSE
,
"priority"
,
_
(
"Priority"
),
priority_value
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
"nick"
,
_
(
"Nickname"
),
purple_value_new
(
G_TYPE_STRING
),
"buzz"
,
_
(
"Allow Buzz"
),
buzz_enabled
,
NULL
);
types
=
g_list_prepend
(
types
,
type
);
priority_value
=
purple_value_new
(
G_TYPE_INT
);
g_value_set_int
(
priority_value
,
1
);
buzz_enabled
=
purple_value_new
(
G_TYPE_BOOLEAN
);
g_value_set_boolean
(
buzz_enabled
,
TRUE
);
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_AVAILABLE
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_CHAT
),
_
(
"Chatty"
),
TRUE
,
TRUE
,
FALSE
,
"priority"
,
_
(
"Priority"
),
priority_value
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
"nick"
,
_
(
"Nickname"
),
purple_value_new
(
G_TYPE_STRING
),
"buzz"
,
_
(
"Allow Buzz"
),
buzz_enabled
,
NULL
);
types
=
g_list_prepend
(
types
,
type
);
priority_value
=
purple_value_new
(
G_TYPE_INT
);
g_value_set_int
(
priority_value
,
0
);
buzz_enabled
=
purple_value_new
(
G_TYPE_BOOLEAN
);
g_value_set_boolean
(
buzz_enabled
,
TRUE
);
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_AWAY
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_AWAY
),
NULL
,
TRUE
,
TRUE
,
FALSE
,
"priority"
,
_
(
"Priority"
),
priority_value
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
"nick"
,
_
(
"Nickname"
),
purple_value_new
(
G_TYPE_STRING
),
"buzz"
,
_
(
"Allow Buzz"
),
buzz_enabled
,
NULL
);
types
=
g_list_prepend
(
types
,
type
);
priority_value
=
purple_value_new
(
G_TYPE_INT
);
g_value_set_int
(
priority_value
,
0
);
buzz_enabled
=
purple_value_new
(
G_TYPE_BOOLEAN
);
g_value_set_boolean
(
buzz_enabled
,
TRUE
);
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_EXTENDED_AWAY
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_XA
),
NULL
,
TRUE
,
TRUE
,
FALSE
,
"priority"
,
_
(
"Priority"
),
priority_value
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
"nick"
,
_
(
"Nickname"
),
purple_value_new
(
G_TYPE_STRING
),
"buzz"
,
_
(
"Allow Buzz"
),
buzz_enabled
,
NULL
);
types
=
g_list_prepend
(
types
,
type
);
priority_value
=
purple_value_new
(
G_TYPE_INT
);
g_value_set_int
(
priority_value
,
0
);
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_UNAVAILABLE
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_DND
),
_
(
"Do Not Disturb"
),
TRUE
,
TRUE
,
FALSE
,
"priority"
,
_
(
"Priority"
),
priority_value
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
"nick"
,
_
(
"Nickname"
),
purple_value_new
(
G_TYPE_STRING
),
NULL
);
types
=
g_list_prepend
(
types
,
type
);
/*
if(js->protocol_version == JABBER_PROTO_0_9)
"Invisible"
*/
type
=
purple_status_type_new_with_attrs
(
PURPLE_STATUS_OFFLINE
,
jabber_buddy_state_get_status_id
(
JABBER_BUDDY_STATE_UNAVAILABLE
),
NULL
,
TRUE
,
TRUE
,
FALSE
,
"message"
,
_
(
"Message"
),
purple_value_new
(
G_TYPE_STRING
),
NULL
);
types
=
g_list_prepend
(
types
,
type
);
return
g_list_reverse
(
types
);
}
static
void
jabber_password_change_result_cb
(
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
from
,
JabberIqType
type
,
G_GNUC_UNUSED
const
char
*
id
,
PurpleXmlNode
*
packet
,
gpointer
data
)
{
if
(
type
==
JABBER_IQ_RESULT
)
{
PurpleAccount
*
account
=
purple_connection_get_account
(
js
->
gc
);
PurpleCredentialManager
*
manager
=
NULL
;
purple_notify_info
(
js
->
gc
,
_
(
"Password Changed"
),
_
(
"Password "
"Changed"
),
_
(
"Your password has been changed."
),
purple_request_cpar_from_connection
(
js
->
gc
));
manager
=
purple_credential_manager_get_default
();
purple_credential_manager_write_password_async
(
manager
,
account
,
(
const
gchar
*
)
data
,
NULL
,
NULL
,
NULL
);
}
else
{
char
*
msg
=
jabber_parse_error
(
js
,
packet
,
NULL
);
purple_notify_error
(
js
->
gc
,
_
(
"Error changing password"
),
_
(
"Error changing password"
),
msg
,
purple_request_cpar_from_connection
(
js
->
gc
));
g_free
(
msg
);
}
g_free
(
data
);
}
static
void
jabber_password_change_cb
(
JabberStream
*
js
,
PurpleRequestPage
*
page
)
{
const
char
*
p1
,
*
p2
;
JabberIq
*
iq
;
PurpleXmlNode
*
query
,
*
y
;
p1
=
purple_request_page_get_string
(
page
,
"password1"
);
p2
=
purple_request_page_get_string
(
page
,
"password2"
);
if
(
!
purple_strequal
(
p1
,
p2
))
{
purple_notify_error
(
js
->
gc
,
NULL
,
_
(
"New passwords do not match."
),
NULL
,
purple_request_cpar_from_connection
(
js
->
gc
));
return
;
}
iq
=
jabber_iq_new_query
(
js
,
JABBER_IQ_SET
,
"jabber:iq:register"
);
purple_xmlnode_set_attrib
(
iq
->
node
,
"to"
,
js
->
user
->
domain
);
query
=
purple_xmlnode_get_child
(
iq
->
node
,
"query"
);
y
=
purple_xmlnode_new_child
(
query
,
"username"
);
purple_xmlnode_insert_data
(
y
,
js
->
user
->
node
,
-1
);
y
=
purple_xmlnode_new_child
(
query
,
"password"
);
purple_xmlnode_insert_data
(
y
,
p1
,
-1
);
jabber_iq_set_callback
(
iq
,
jabber_password_change_result_cb
,
g_strdup
(
p1
));
jabber_iq_send
(
iq
);
}
static
void
jabber_password_change
(
G_GNUC_UNUSED
GSimpleAction
*
action
,
GVariant
*
parameter
,
G_GNUC_UNUSED
gpointer
data
)
{
const
char
*
account_id
=
NULL
;
PurpleAccountManager
*
manager
=
NULL
;
PurpleAccount
*
account
=
NULL
;
PurpleConnection
*
connection
=
NULL
;
JabberStream
*
js
=
NULL
;
PurpleRequestPage
*
page
;
PurpleRequestGroup
*
group
;
PurpleRequestField
*
field
;
if
(
!
g_variant_is_of_type
(
parameter
,
G_VARIANT_TYPE_STRING
))
{
g_critical
(
"XMPP Change Password action parameter is of incorrect type %s"
,
g_variant_get_type_string
(
parameter
));
}
account_id
=
g_variant_get_string
(
parameter
,
NULL
);
manager
=
purple_account_manager_get_default
();
account
=
purple_account_manager_find_by_id
(
manager
,
account_id
);
connection
=
purple_account_get_connection
(
account
);
js
=
purple_connection_get_protocol_data
(
connection
);
page
=
purple_request_page_new
();
group
=
purple_request_group_new
(
NULL
);
purple_request_page_add_group
(
page
,
group
);
field
=
purple_request_field_string_new
(
"password1"
,
_
(
"Password"
),
""
,
FALSE
);
purple_request_field_string_set_masked
(
PURPLE_REQUEST_FIELD_STRING
(
field
),
TRUE
);
purple_request_field_set_required
(
field
,
TRUE
);
purple_request_group_add_field
(
group
,
field
);
field
=
purple_request_field_string_new
(
"password2"
,
_
(
"Password (again)"
),
""
,
FALSE
);
purple_request_field_string_set_masked
(
PURPLE_REQUEST_FIELD_STRING
(
field
),
TRUE
);
purple_request_field_set_required
(
field
,
TRUE
);
purple_request_group_add_field
(
group
,
field
);
purple_request_fields
(
connection
,
_
(
"Change XMPP Password"
),
_
(
"Change XMPP Password"
),
_
(
"Please enter your new password"
),
page
,
_
(
"OK"
),
G_CALLBACK
(
jabber_password_change_cb
),
_
(
"Cancel"
),
NULL
,
purple_request_cpar_from_connection
(
connection
),
js
);
}
static
const
gchar
*
xmpp_protocol_actions_get_prefix
(
G_GNUC_UNUSED
PurpleProtocolActions
*
actions
)
{
return
"prpl-xmpp"
;
}
static
GActionGroup
*
xmpp_protocol_actions_get_action_group
(
G_GNUC_UNUSED
PurpleProtocolActions
*
actions
,
PurpleConnection
*
connection
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
connection
);
GSimpleActionGroup
*
group
=
NULL
;
GActionEntry
entries
[]
=
{
{
.
name
=
"set-user-info"
,
.
activate
=
jabber_setup_set_info
,
.
parameter_type
=
"s"
,
},
{
.
name
=
"change-password"
,
.
activate
=
jabber_password_change
,
.
parameter_type
=
"s"
,
},
};
gsize
nentries
=
G_N_ELEMENTS
(
entries
);
group
=
g_simple_action_group_new
();
g_action_map_add_action_entries
(
G_ACTION_MAP
(
group
),
entries
,
nentries
,
NULL
);
if
(
js
->
pep
)
{
jabber_pep_add_action_entries
(
group
);
}
#if 0
if(js->commands) {
jabber_adhoc_add_server_action_entries(js, group);
}
#endif
return
G_ACTION_GROUP
(
group
);
}
static
GMenu
*
xmpp_protocol_actions_get_menu
(
G_GNUC_UNUSED
PurpleProtocolActions
*
actions
,
G_GNUC_UNUSED
PurpleConnection
*
connection
)
{
GMenu
*
menu
=
NULL
;
GMenuItem
*
item
=
NULL
;
menu
=
g_menu_new
();
item
=
g_menu_item_new
(
_
(
"Set User Info..."
),
"prpl-xmpp.set-user-info"
);
g_menu_item_set_attribute
(
item
,
PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET
,
"s"
,
"account"
);
g_menu_append_item
(
menu
,
item
);
g_object_unref
(
item
);
item
=
g_menu_item_new
(
_
(
"Change Password..."
),
"prpl-xmpp.change-password"
);
g_menu_item_set_attribute
(
item
,
PURPLE_MENU_ATTRIBUTE_DYNAMIC_TARGET
,
"s"
,
"account"
);
g_menu_append_item
(
menu
,
item
);
g_object_unref
(
item
);
jabber_pep_append_menu
(
menu
);
#if 0
if(js->commands) {
jabber_adhoc_append_server_menu(js, menu);
}
#endif
return
menu
;
}
static
PurpleChat
*
jabber_find_blist_chat
(
G_GNUC_UNUSED
PurpleProtocolClient
*
client
,
PurpleAccount
*
account
,
const
char
*
name
)
{
PurpleBlistNode
*
gnode
,
*
cnode
;
JabberID
*
jid
;
if
(
!
(
jid
=
jabber_id_new
(
name
)))
return
NULL
;
for
(
gnode
=
purple_blist_get_default_root
();
gnode
;
gnode
=
purple_blist_node_get_sibling_next
(
gnode
))
{
for
(
cnode
=
purple_blist_node_get_first_child
(
gnode
);
cnode
;
cnode
=
purple_blist_node_get_sibling_next
(
cnode
))
{
PurpleChat
*
chat
=
(
PurpleChat
*
)
cnode
;
const
char
*
room
,
*
server
;
GHashTable
*
components
;
if
(
!
PURPLE_IS_CHAT
(
cnode
))
continue
;
if
(
purple_chat_get_account
(
chat
)
!=
account
)
continue
;
components
=
purple_chat_get_components
(
chat
);
if
(
!
(
room
=
g_hash_table_lookup
(
components
,
"room"
)))
continue
;
if
(
!
(
server
=
g_hash_table_lookup
(
components
,
"server"
)))
continue
;
/* FIXME: Collate is wrong in a few cases here; this should be prepped */
if
(
jid
->
node
&&
jid
->
domain
&&
!
g_utf8_collate
(
room
,
jid
->
node
)
&&
!
g_utf8_collate
(
server
,
jid
->
domain
))
{
jabber_id_free
(
jid
);
return
chat
;
}
}
}
jabber_id_free
(
jid
);
return
NULL
;
}
static
void
jabber_convo_closed
(
G_GNUC_UNUSED
PurpleProtocolClient
*
client
,
PurpleConnection
*
gc
,
const
char
*
who
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
JabberID
*
jid
;
JabberBuddy
*
jb
;
JabberBuddyResource
*
jbr
;
if
(
!
(
jid
=
jabber_id_new
(
who
)))
return
;
if
((
jb
=
jabber_buddy_find
(
js
,
who
,
TRUE
))
&&
(
jbr
=
jabber_buddy_find_resource
(
jb
,
jid
->
resource
)))
{
g_free
(
jbr
->
thread_id
);
jbr
->
thread_id
=
NULL
;
}
jabber_id_free
(
jid
);
}
static
const
gchar
*
jabber_client_normalize
(
G_GNUC_UNUSED
PurpleProtocolClient
*
client
,
PurpleAccount
*
account
,
const
char
*
who
)
{
return
jabber_normalize
(
account
,
who
);
}
char
*
jabber_parse_error
(
JabberStream
*
js
,
PurpleXmlNode
*
packet
,
PurpleConnectionError
*
reason
)
{
PurpleXmlNode
*
error
;
const
char
*
code
=
NULL
,
*
text
=
NULL
;
const
char
*
xmlns
=
purple_xmlnode_get_namespace
(
packet
);
char
*
cdata
=
NULL
;
#define SET_REASON(x) \
if(reason != NULL) { *reason = x; }
if
((
error
=
purple_xmlnode_get_child
(
packet
,
"error"
)))
{
PurpleXmlNode
*
t
=
purple_xmlnode_get_child_with_namespace
(
error
,
"text"
,
NS_XMPP_STANZAS
);
if
(
t
)
cdata
=
purple_xmlnode_get_data
(
t
);
code
=
purple_xmlnode_get_attrib
(
error
,
"code"
);
/* Stanza errors */
if
(
purple_xmlnode_get_child
(
error
,
"bad-request"
))
{
text
=
_
(
"Bad Request"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"conflict"
))
{
SET_REASON
(
PURPLE_CONNECTION_ERROR_NAME_IN_USE
);
text
=
_
(
"Conflict"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"feature-not-implemented"
))
{
text
=
_
(
"Feature Not Implemented"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"forbidden"
))
{
text
=
_
(
"Forbidden"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"gone"
))
{
text
=
_
(
"Gone"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"internal-server-error"
))
{
text
=
_
(
"Internal Server Error"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"item-not-found"
))
{
text
=
_
(
"Item Not Found"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"jid-malformed"
))
{
text
=
_
(
"Malformed XMPP ID"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"not-acceptable"
))
{
text
=
_
(
"Not Acceptable"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"not-allowed"
))
{
text
=
_
(
"Not Allowed"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"not-authorized"
))
{
text
=
_
(
"Not Authorized"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"payment-required"
))
{
text
=
_
(
"Payment Required"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"recipient-unavailable"
))
{
text
=
_
(
"Recipient Unavailable"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"redirect"
))
{
/* XXX */
}
else
if
(
purple_xmlnode_get_child
(
error
,
"registration-required"
))
{
text
=
_
(
"Registration Required"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"remote-server-not-found"
))
{
text
=
_
(
"Remote Server Not Found"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"remote-server-timeout"
))
{
text
=
_
(
"Remote Server Timeout"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"resource-constraint"
))
{
text
=
_
(
"Server Overloaded"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"service-unavailable"
))
{
text
=
_
(
"Service Unavailable"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"subscription-required"
))
{
text
=
_
(
"Subscription Required"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"unexpected-request"
))
{
text
=
_
(
"Unexpected Request"
);
}
else
if
(
purple_xmlnode_get_child
(
error
,
"undefined-condition"
))
{
text
=
_
(
"Unknown Error"
);
}
}
else
if
(
purple_strequal
(
xmlns
,
NS_XMPP_SASL
))
{
/* Most common reason can be the default */
SET_REASON
(
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
);
if
(
purple_xmlnode_get_child
(
packet
,
"aborted"
))
{
text
=
_
(
"Authorization Aborted"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"incorrect-encoding"
))
{
text
=
_
(
"Incorrect encoding in authorization"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"invalid-authzid"
))
{
text
=
_
(
"Invalid authzid"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"invalid-mechanism"
))
{
text
=
_
(
"Invalid Authorization Mechanism"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"mechanism-too-weak"
))
{
SET_REASON
(
PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE
);
text
=
_
(
"Authorization mechanism too weak"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"not-authorized"
))
{
SET_REASON
(
PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED
);
/* Clear the password if it isn't being saved */
if
(
!
purple_account_get_remember_password
(
purple_connection_get_account
(
js
->
gc
)))
{
PurpleAccount
*
account
=
purple_connection_get_account
(
js
->
gc
);
PurpleCredentialManager
*
manager
=
NULL
;
manager
=
purple_credential_manager_get_default
();
purple_credential_manager_clear_password_async
(
manager
,
account
,
NULL
,
NULL
,
NULL
);
}
text
=
_
(
"Not Authorized"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"temporary-auth-failure"
))
{
text
=
_
(
"Temporary Authentication Failure"
);
}
else
{
SET_REASON
(
PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED
);
text
=
_
(
"Authentication Failure"
);
}
}
else
if
(
purple_strequal
(
packet
->
name
,
"stream:error"
)
||
(
purple_strequal
(
packet
->
name
,
"error"
)
&&
purple_strequal
(
xmlns
,
NS_XMPP_STREAMS
)))
{
/* Most common reason as default: */
SET_REASON
(
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
);
if
(
purple_xmlnode_get_child
(
packet
,
"bad-format"
))
{
text
=
_
(
"Bad Format"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"bad-namespace-prefix"
))
{
text
=
_
(
"Bad Namespace Prefix"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"conflict"
))
{
SET_REASON
(
PURPLE_CONNECTION_ERROR_NAME_IN_USE
);
text
=
_
(
"Resource Conflict"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"connection-timeout"
))
{
text
=
_
(
"Connection Timeout"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"host-gone"
))
{
text
=
_
(
"Host Gone"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"host-unknown"
))
{
text
=
_
(
"Host Unknown"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"improper-addressing"
))
{
text
=
_
(
"Improper Addressing"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"internal-server-error"
))
{
text
=
_
(
"Internal Server Error"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"invalid-id"
))
{
text
=
_
(
"Invalid ID"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"invalid-namespace"
))
{
text
=
_
(
"Invalid Namespace"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"invalid-xml"
))
{
text
=
_
(
"Invalid XML"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"nonmatching-hosts"
))
{
text
=
_
(
"Non-matching Hosts"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"not-authorized"
))
{
text
=
_
(
"Not Authorized"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"policy-violation"
))
{
text
=
_
(
"Policy Violation"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"remote-connection-failed"
))
{
text
=
_
(
"Remote Connection Failed"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"resource-constraint"
))
{
text
=
_
(
"Resource Constraint"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"restricted-xml"
))
{
text
=
_
(
"Restricted XML"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"see-other-host"
))
{
text
=
_
(
"See Other Host"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"system-shutdown"
))
{
text
=
_
(
"System Shutdown"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"undefined-condition"
))
{
text
=
_
(
"Undefined Condition"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"unsupported-encoding"
))
{
text
=
_
(
"Unsupported Encoding"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"unsupported-stanza-type"
))
{
text
=
_
(
"Unsupported Stanza Type"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"unsupported-version"
))
{
text
=
_
(
"Unsupported Version"
);
}
else
if
(
purple_xmlnode_get_child
(
packet
,
"xml-not-well-formed"
))
{
text
=
_
(
"XML Not Well Formed"
);
}
else
{
text
=
_
(
"Stream Error"
);
}
}
#undef SET_REASON
if
(
text
||
cdata
)
{
char
*
ret
=
g_strdup_printf
(
"%s%s%s"
,
code
?
code
:
""
,
code
?
": "
:
""
,
text
?
text
:
cdata
);
g_free
(
cdata
);
return
ret
;
}
else
{
return
NULL
;
}
}
static
PurpleCmdRet
jabber_cmd_chat_config
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
G_GNUC_UNUSED
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
)
return
PURPLE_CMD_RET_FAILED
;
jabber_chat_request_room_configure
(
chat
);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_register
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
G_GNUC_UNUSED
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
)
return
PURPLE_CMD_RET_FAILED
;
jabber_chat_register
(
chat
);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_topic
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
)
return
PURPLE_CMD_RET_FAILED
;
if
(
args
&&
args
[
0
]
&&
*
args
[
0
])
jabber_chat_change_topic
(
chat
,
args
[
0
]);
else
{
const
char
*
cur
=
purple_chat_conversation_get_topic
(
PURPLE_CHAT_CONVERSATION
(
conv
));
char
*
buf
,
*
tmp
,
*
tmp2
;
if
(
cur
)
{
tmp
=
g_markup_escape_text
(
cur
,
-1
);
tmp2
=
purple_markup_linkify
(
tmp
);
buf
=
g_strdup_printf
(
_
(
"current topic is: %s"
),
tmp2
);
g_free
(
tmp
);
g_free
(
tmp2
);
}
else
buf
=
g_strdup
(
_
(
"No topic is set"
));
purple_conversation_write_system_message
(
conv
,
buf
,
PURPLE_MESSAGE_NO_LOG
);
g_free
(
buf
);
}
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_nick
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
if
(
!
jabber_resourceprep_validate
(
args
[
0
]))
{
*
error
=
g_strdup
(
_
(
"Invalid nickname"
));
return
PURPLE_CMD_RET_FAILED
;
}
if
(
jabber_chat_change_nick
(
chat
,
args
[
0
]))
return
PURPLE_CMD_RET_OK
;
else
return
PURPLE_CMD_RET_FAILED
;
}
static
PurpleCmdRet
jabber_cmd_chat_part
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
)
return
PURPLE_CMD_RET_FAILED
;
jabber_chat_part
(
chat
,
args
?
args
[
0
]
:
NULL
);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_ban
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
if
(
!
jabber_chat_ban_user
(
chat
,
args
[
0
],
args
[
1
]))
{
*
error
=
g_strdup_printf
(
_
(
"Unable to ban user %s"
),
args
[
0
]);
return
PURPLE_CMD_RET_FAILED
;
}
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_affiliate
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
if
(
!
purple_strequal
(
args
[
0
],
"owner"
)
&&
!
purple_strequal
(
args
[
0
],
"admin"
)
&&
!
purple_strequal
(
args
[
0
],
"member"
)
&&
!
purple_strequal
(
args
[
0
],
"outcast"
)
&&
!
purple_strequal
(
args
[
0
],
"none"
))
{
*
error
=
g_strdup_printf
(
_
(
"Unknown affiliation:
\"
%s
\"
"
),
args
[
0
]);
return
PURPLE_CMD_RET_FAILED
;
}
if
(
args
[
1
])
{
int
i
;
char
**
nicks
=
g_strsplit
(
args
[
1
],
" "
,
-1
);
for
(
i
=
0
;
nicks
[
i
];
++
i
)
if
(
!
jabber_chat_affiliate_user
(
chat
,
nicks
[
i
],
args
[
0
]))
{
*
error
=
g_strdup_printf
(
_
(
"Unable to affiliate user %s as
\"
%s
\"
"
),
nicks
[
i
],
args
[
0
]);
g_strfreev
(
nicks
);
return
PURPLE_CMD_RET_FAILED
;
}
g_strfreev
(
nicks
);
}
else
{
jabber_chat_affiliation_list
(
chat
,
args
[
0
]);
}
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_role
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
if
(
!
purple_strequal
(
args
[
0
],
"moderator"
)
&&
!
purple_strequal
(
args
[
0
],
"participant"
)
&&
!
purple_strequal
(
args
[
0
],
"visitor"
)
&&
!
purple_strequal
(
args
[
0
],
"none"
))
{
*
error
=
g_strdup_printf
(
_
(
"Unknown role:
\"
%s
\"
"
),
args
[
0
]);
return
PURPLE_CMD_RET_FAILED
;
}
if
(
args
[
1
])
{
int
i
;
char
**
nicks
=
g_strsplit
(
args
[
1
],
" "
,
-1
);
for
(
i
=
0
;
nicks
[
i
];
i
++
)
if
(
!
jabber_chat_role_user
(
chat
,
nicks
[
i
],
args
[
0
],
NULL
))
{
*
error
=
g_strdup_printf
(
_
(
"Unable to set role
\"
%s
\"
for user: %s"
),
args
[
0
],
nicks
[
i
]);
g_strfreev
(
nicks
);
return
PURPLE_CMD_RET_FAILED
;
}
g_strfreev
(
nicks
);
}
else
{
jabber_chat_role_list
(
chat
,
args
[
0
]);
}
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_invite
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
if
(
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
jabber_chat_invite
(
purple_conversation_get_connection
(
conv
),
purple_chat_conversation_get_id
(
PURPLE_CHAT_CONVERSATION
(
conv
)),
args
[
1
]
?
args
[
1
]
:
""
,
args
[
0
]);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_join
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
gpointer
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
GHashTable
*
components
;
JabberID
*
jid
=
NULL
;
const
char
*
room
=
NULL
,
*
server
=
NULL
,
*
handle
=
NULL
;
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
components
=
g_hash_table_new_full
(
g_str_hash
,
g_str_equal
,
NULL
,
NULL
);
if
(
strchr
(
args
[
0
],
'@'
))
jid
=
jabber_id_new
(
args
[
0
]);
if
(
jid
)
{
room
=
jid
->
node
;
server
=
jid
->
domain
;
handle
=
jid
->
resource
?
jid
->
resource
:
chat
->
handle
;
}
else
{
/* If jabber_id_new failed, the user may have just passed in
* a room name. For backward compatibility, handle that here.
*/
if
(
strchr
(
args
[
0
],
'@'
))
{
*
error
=
g_strdup
(
_
(
"Invalid XMPP ID"
));
return
PURPLE_CMD_RET_FAILED
;
}
room
=
args
[
0
];
server
=
chat
->
server
;
handle
=
chat
->
handle
;
}
g_hash_table_insert
(
components
,
"room"
,
(
gpointer
)
room
);
g_hash_table_insert
(
components
,
"server"
,
(
gpointer
)
server
);
g_hash_table_insert
(
components
,
"handle"
,
(
gpointer
)
handle
);
if
(
args
[
1
])
g_hash_table_insert
(
components
,
"password"
,
args
[
1
]);
jabber_chat_join
(
purple_conversation_get_connection
(
conv
),
components
);
g_hash_table_destroy
(
components
);
jabber_id_free
(
jid
);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_kick
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
void
*
data
)
{
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
if
(
!
chat
||
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
if
(
!
jabber_chat_role_user
(
chat
,
args
[
0
],
"none"
,
args
[
1
]))
{
*
error
=
g_strdup_printf
(
_
(
"Unable to kick user %s"
),
args
[
0
]);
return
PURPLE_CMD_RET_FAILED
;
}
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_chat_msg
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
G_GNUC_UNUSED
char
**
error
,
G_GNUC_UNUSED
void
*
data
)
{
PurpleAccount
*
account
=
NULL
;
PurpleConnection
*
pc
=
NULL
;
PurpleProtocol
*
prpl
=
NULL
;
PurpleMessage
*
msg
=
NULL
;
JabberChat
*
chat
=
jabber_chat_find_by_conv
(
PURPLE_CHAT_CONVERSATION
(
conv
));
char
*
who
;
const
gchar
*
me
=
NULL
;
if
(
!
chat
)
return
PURPLE_CMD_RET_FAILED
;
account
=
purple_connection_get_account
(
pc
);
me
=
purple_contact_info_get_name_for_display
(
PURPLE_CONTACT_INFO
(
account
));
who
=
g_strdup_printf
(
"%s@%s/%s"
,
chat
->
room
,
chat
->
server
,
args
[
0
]);
pc
=
purple_conversation_get_connection
(
conv
);
prpl
=
purple_connection_get_protocol
(
pc
);
msg
=
purple_message_new_outgoing
(
account
,
me
,
who
,
args
[
1
],
0
);
jabber_message_send_im
(
PURPLE_PROTOCOL_IM
(
prpl
),
pc
,
msg
);
g_free
(
who
);
return
PURPLE_CMD_RET_OK
;
}
static
PurpleCmdRet
jabber_cmd_ping
(
PurpleConversation
*
conv
,
G_GNUC_UNUSED
const
char
*
cmd
,
char
**
args
,
char
**
error
,
G_GNUC_UNUSED
void
*
data
)
{
PurpleAccount
*
account
;
PurpleConnection
*
pc
;
if
(
!
args
||
!
args
[
0
])
return
PURPLE_CMD_RET_FAILED
;
account
=
purple_conversation_get_account
(
conv
);
pc
=
purple_account_get_connection
(
account
);
if
(
!
jabber_ping_jid
(
purple_connection_get_protocol_data
(
pc
),
args
[
0
]))
{
*
error
=
g_strdup_printf
(
_
(
"Unable to ping user %s"
),
args
[
0
]);
return
PURPLE_CMD_RET_FAILED
;
}
return
PURPLE_CMD_RET_OK
;
}
static
gboolean
jabber_offline_message
(
G_GNUC_UNUSED
PurpleProtocolClient
*
client
,
G_GNUC_UNUSED
PurpleBuddy
*
buddy
)
{
return
TRUE
;
}
static
gboolean
jabber_audio_enabled
(
G_GNUC_UNUSED
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
namespace
)
{
PurpleMediaManager
*
manager
=
purple_media_manager_get
();
PurpleMediaCaps
caps
=
purple_media_manager_get_ui_caps
(
manager
);
return
(
caps
&
(
PURPLE_MEDIA_CAPS_AUDIO
|
PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION
));
}
static
gboolean
jabber_video_enabled
(
G_GNUC_UNUSED
JabberStream
*
js
,
G_GNUC_UNUSED
const
char
*
namespace
)
{
PurpleMediaManager
*
manager
=
purple_media_manager_get
();
PurpleMediaCaps
caps
=
purple_media_manager_get_ui_caps
(
manager
);
return
(
caps
&
(
PURPLE_MEDIA_CAPS_VIDEO
|
PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION
));
}
typedef
struct
{
PurpleProtocolMedia
*
media
;
PurpleAccount
*
account
;
gchar
*
who
;
PurpleMediaSessionType
type
;
}
JabberMediaRequest
;
static
void
jabber_media_cancel_cb
(
JabberMediaRequest
*
request
,
G_GNUC_UNUSED
PurpleRequestPage
*
page
)
{
g_free
(
request
->
who
);
g_free
(
request
);
}
static
void
jabber_media_ok_cb
(
JabberMediaRequest
*
request
,
PurpleRequestPage
*
page
)
{
const
gchar
*
selected
=
purple_request_page_get_choice
(
page
,
"resource"
);
gchar
*
who
=
g_strdup_printf
(
"%s/%s"
,
request
->
who
,
selected
);
jabber_initiate_media
(
request
->
media
,
request
->
account
,
who
,
request
->
type
);
g_free
(
who
);
g_free
(
request
->
who
);
g_free
(
request
);
}
static
gboolean
jabber_initiate_media
(
PurpleProtocolMedia
*
media
,
PurpleAccount
*
account
,
const
gchar
*
who
,
PurpleMediaSessionType
type
)
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
JabberBuddy
*
jb
;
JabberBuddyResource
*
jbr
=
NULL
;
char
*
resource
=
NULL
;
if
(
!
js
)
{
purple_debug_error
(
"jabber"
,
"jabber_initiate_media: NULL stream
\n
"
);
return
FALSE
;
}
jb
=
jabber_buddy_find
(
js
,
who
,
FALSE
);
if
(
!
jb
||
!
jb
->
resources
||
(((
resource
=
jabber_get_resource
(
who
))
!=
NULL
)
&&
(
jbr
=
jabber_buddy_find_resource
(
jb
,
resource
))
==
NULL
))
{
/* no resources online, we're trying to initiate with someone
* whose presence we're not subscribed to, or
* someone who is offline. Let's inform the user */
char
*
msg
;
if
(
!
jb
)
{
msg
=
g_strdup_printf
(
_
(
"Unable to initiate media with %s: invalid JID"
),
who
);
}
else
if
(
jb
->
subscription
&
JABBER_SUB_TO
&&
!
jb
->
resources
)
{
msg
=
g_strdup_printf
(
_
(
"Unable to initiate media with %s: user is not online"
),
who
);
}
else
if
(
resource
)
{
msg
=
g_strdup_printf
(
_
(
"Unable to initiate media with %s: resource is not online"
),
who
);
}
else
{
msg
=
g_strdup_printf
(
_
(
"Unable to initiate media with %s: not subscribed to user presence"
),
who
);
}
purple_notify_error
(
account
,
_
(
"Media Initiation Failed"
),
_
(
"Media Initiation Failed"
),
msg
,
purple_request_cpar_from_connection
(
gc
));
g_free
(
msg
);
g_free
(
resource
);
return
FALSE
;
}
else
if
(
jbr
!=
NULL
)
{
/* they've specified a resource, no need to ask or
* default or anything, just do it */
g_free
(
resource
);
return
jingle_rtp_initiate_media
(
js
,
who
,
type
);
}
else
if
(
!
jb
->
resources
->
next
)
{
/* only 1 resource online (probably our most common case)
* so no need to ask who to initiate with */
gchar
*
name
;
gboolean
result
;
jbr
=
jb
->
resources
->
data
;
name
=
g_strdup_printf
(
"%s/%s"
,
who
,
jbr
->
name
);
result
=
jabber_initiate_media
(
media
,
account
,
name
,
type
);
g_free
(
name
);
return
result
;
}
else
{
/* we've got multiple resources,
* we need to pick one to initiate with */
GList
*
l
;
char
*
msg
;
PurpleRequestPage
*
page
=
NULL
;
PurpleRequestField
*
field
=
NULL
;
PurpleRequestFieldChoice
*
choice
=
NULL
;
PurpleRequestGroup
*
group
=
NULL
;
JabberMediaRequest
*
request
;
field
=
purple_request_field_choice_new
(
"resource"
,
_
(
"Resource"
),
0
);
choice
=
PURPLE_REQUEST_FIELD_CHOICE
(
field
);
for
(
l
=
jb
->
resources
;
l
;
l
=
l
->
next
)
{
JabberBuddyResource
*
ljbr
=
l
->
data
;
PurpleMediaCaps
caps
;
gchar
*
name
;
name
=
g_strdup_printf
(
"%s/%s"
,
who
,
ljbr
->
name
);
caps
=
jabber_get_media_caps
(
media
,
account
,
name
);
g_free
(
name
);
if
((
type
&
PURPLE_MEDIA_AUDIO
)
&&
(
type
&
PURPLE_MEDIA_VIDEO
))
{
if
(
caps
&
PURPLE_MEDIA_CAPS_AUDIO_VIDEO
)
{
jbr
=
ljbr
;
purple_request_field_choice_add_full
(
choice
,
jbr
->
name
,
g_strdup
(
jbr
->
name
),
g_free
);
}
}
else
if
(
type
&
(
PURPLE_MEDIA_AUDIO
)
&&
(
caps
&
PURPLE_MEDIA_CAPS_AUDIO
))
{
jbr
=
ljbr
;
purple_request_field_choice_add_full
(
choice
,
jbr
->
name
,
g_strdup
(
jbr
->
name
),
g_free
);
}
else
if
(
type
&
(
PURPLE_MEDIA_VIDEO
)
&&
(
caps
&
PURPLE_MEDIA_CAPS_VIDEO
))
{
jbr
=
ljbr
;
purple_request_field_choice_add_full
(
choice
,
jbr
->
name
,
g_strdup
(
jbr
->
name
),
g_free
);
}
}
if
(
jbr
==
NULL
)
{
purple_debug_error
(
"jabber"
,
"No resources available
\n
"
);
return
FALSE
;
}
if
(
g_list_length
(
purple_request_field_choice_get_elements
(
choice
))
<=
1
)
{
gchar
*
name
;
gboolean
result
;
g_object_unref
(
field
);
name
=
g_strdup_printf
(
"%s/%s"
,
who
,
jbr
->
name
);
result
=
jabber_initiate_media
(
media
,
account
,
name
,
type
);
g_free
(
name
);
return
result
;
}
msg
=
g_strdup_printf
(
_
(
"Please select the resource of %s with which you would like to start a media session."
),
who
);
page
=
purple_request_page_new
();
group
=
purple_request_group_new
(
NULL
);
request
=
g_new0
(
JabberMediaRequest
,
1
);
request
->
media
=
media
;
request
->
account
=
account
;
request
->
who
=
g_strdup
(
who
);
request
->
type
=
type
;
purple_request_group_add_field
(
group
,
field
);
purple_request_page_add_group
(
page
,
group
);
purple_request_fields
(
account
,
_
(
"Select a Resource"
),
msg
,
NULL
,
page
,
_
(
"Initiate Media"
),
G_CALLBACK
(
jabber_media_ok_cb
),
_
(
"Cancel"
),
G_CALLBACK
(
jabber_media_cancel_cb
),
purple_request_cpar_from_account
(
account
),
request
);
g_free
(
msg
);
return
TRUE
;
}
return
FALSE
;
}
static
PurpleMediaCaps
jabber_get_media_caps
(
G_GNUC_UNUSED
PurpleProtocolMedia
*
media
,
PurpleAccount
*
account
,
const
char
*
who
)
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
account
);
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
JabberBuddy
*
jb
;
JabberBuddyResource
*
jbr
;
PurpleMediaCaps
total
=
PURPLE_MEDIA_CAPS_NONE
;
gchar
*
resource
;
GList
*
specific
=
NULL
,
*
l
;
if
(
!
js
)
{
purple_debug_info
(
"jabber"
,
"jabber_can_do_media: NULL stream
\n
"
);
return
FALSE
;
}
jb
=
jabber_buddy_find
(
js
,
who
,
FALSE
);
if
(
!
jb
||
!
jb
->
resources
)
{
/* no resources online, we're trying to get caps for someone
* whose presence we're not subscribed to, or
* someone who is offline. */
return
total
;
}
else
if
((
resource
=
jabber_get_resource
(
who
))
!=
NULL
)
{
/* they've specified a resource, no need to ask or
* default or anything, just do it */
jbr
=
jabber_buddy_find_resource
(
jb
,
resource
);
g_free
(
resource
);
if
(
!
jbr
)
{
purple_debug_error
(
"jabber"
,
"jabber_get_media_caps:"
" Can't find resource %s
\n
"
,
who
);
return
total
;
}
l
=
specific
=
g_list_prepend
(
specific
,
jbr
);
}
else
{
/* we've got multiple resources, combine their caps */
l
=
jb
->
resources
;
}
for
(;
l
;
l
=
l
->
next
)
{
PurpleMediaCaps
caps
=
PURPLE_MEDIA_CAPS_NONE
;
jbr
=
l
->
data
;
if
(
jabber_resource_has_capability
(
jbr
,
JINGLE_APP_RTP_SUPPORT_AUDIO
))
caps
|=
PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION
|
PURPLE_MEDIA_CAPS_AUDIO
;
if
(
jabber_resource_has_capability
(
jbr
,
JINGLE_APP_RTP_SUPPORT_VIDEO
))
caps
|=
PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION
|
PURPLE_MEDIA_CAPS_VIDEO
;
if
(
caps
&
PURPLE_MEDIA_CAPS_AUDIO
&&
caps
&
PURPLE_MEDIA_CAPS_VIDEO
)
caps
|=
PURPLE_MEDIA_CAPS_AUDIO_VIDEO
;
if
(
caps
!=
PURPLE_MEDIA_CAPS_NONE
)
{
if
(
!
jabber_resource_has_capability
(
jbr
,
JINGLE_TRANSPORT_ICEUDP
)
&&
!
jabber_resource_has_capability
(
jbr
,
JINGLE_TRANSPORT_RAWUDP
))
{
purple_debug_info
(
"jingle-rtp"
,
"Buddy doesn't "
"support the same transport types
\n
"
);
caps
=
PURPLE_MEDIA_CAPS_NONE
;
}
else
caps
|=
PURPLE_MEDIA_CAPS_MODIFY_SESSION
|
PURPLE_MEDIA_CAPS_CHANGE_DIRECTION
;
}
total
|=
caps
;
}
g_clear_list
(
&
specific
,
NULL
);
return
total
;
}
static
gboolean
jabber_can_receive_file
(
G_GNUC_UNUSED
PurpleProtocolXfer
*
prplxfer
,
PurpleConnection
*
gc
,
const
char
*
who
)
{
JabberStream
*
js
=
purple_connection_get_protocol_data
(
gc
);
if
(
js
)
{
JabberBuddy
*
jb
=
jabber_buddy_find
(
js
,
who
,
FALSE
);
GList
*
iter
;
gboolean
has_resources_without_caps
=
FALSE
;
/* if we didn't find a JabberBuddy, we don't have presence for this
buddy, let's assume they can receive files, disco should tell us
when actually trying */
if
(
jb
==
NULL
)
return
TRUE
;
/* find out if there is any resources without caps */
for
(
iter
=
jb
->
resources
;
iter
;
iter
=
g_list_next
(
iter
))
{
JabberBuddyResource
*
jbr
=
(
JabberBuddyResource
*
)
iter
->
data
;
if
(
!
jabber_resource_know_capabilities
(
jbr
))
{
has_resources_without_caps
=
TRUE
;
}
}
if
(
has_resources_without_caps
)
{
/* there is at least one resource which we don't have caps for,
let's assume they can receive files... */
return
TRUE
;
}
else
{
/* we have caps for all the resources, see if at least one has
right caps */
for
(
iter
=
jb
->
resources
;
iter
;
iter
=
g_list_next
(
iter
))
{
JabberBuddyResource
*
jbr
=
(
JabberBuddyResource
*
)
iter
->
data
;
if
(
jabber_resource_has_capability
(
jbr
,
NS_SI_FILE_TRANSFER
)
&&
(
jabber_resource_has_capability
(
jbr
,
NS_BYTESTREAMS
)
||
jabber_resource_has_capability
(
jbr
,
NS_IBB
)))
{
return
TRUE
;
}
}
return
FALSE
;
}
}
else
{
return
TRUE
;
}
}
static
void
jabber_register_commands
(
PurpleProtocol
*
protocol
)
{
GSList
*
commands
=
NULL
;
PurpleCmdId
id
;
const
gchar
*
proto_id
=
purple_protocol_get_id
(
protocol
);
id
=
purple_cmd_register
(
"config"
,
""
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_chat_config
,
_
(
"config: Configure a chat room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"configure"
,
""
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_chat_config
,
_
(
"configure: Configure a chat room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"nick"
,
"s"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_chat_nick
,
_
(
"nick <new nickname>: "
"Change your nickname."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"part"
,
"s"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_part
,
_
(
"part [message]: Leave the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"register"
,
""
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_chat_register
,
_
(
"register: Register with a chat room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
/* XXX: there needs to be a core /topic cmd, methinks */
id
=
purple_cmd_register
(
"topic"
,
"s"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_topic
,
_
(
"topic [new topic]: View or change the topic."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"ban"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_ban
,
_
(
"ban <user> [reason]: Ban a user from the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"affiliate"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_affiliate
,
_
(
"affiliate "
"<owner|admin|member|outcast|none> [nick1] [nick2] ...: "
"Get the users with an affiliation or set users' affiliation "
"with the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"role"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_role
,
_
(
"role <moderator|participant|visitor|none> [nick1] "
"[nick2] ...: Get the users with a role or set users' role "
"with the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"invite"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_invite
,
_
(
"invite <user> [message]: Invite a user to the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"join"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_join
,
_
(
"join: <room[@server]> [password]: Join a chat."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"kick"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
|
PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS
,
proto_id
,
jabber_cmd_chat_kick
,
_
(
"kick <user> [reason]: Kick a user from the room."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"msg"
,
"ws"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_chat_msg
,
_
(
"msg <user> <message>: "
"Send a private message to another user."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
id
=
purple_cmd_register
(
"ping"
,
"w"
,
PURPLE_CMD_P_PROTOCOL
,
PURPLE_CMD_FLAG_CHAT
|
PURPLE_CMD_FLAG_IM
|
PURPLE_CMD_FLAG_PROTOCOL_ONLY
,
proto_id
,
jabber_cmd_ping
,
_
(
"ping <jid>: Ping a user/component/server."
),
NULL
);
commands
=
g_slist_prepend
(
commands
,
GUINT_TO_POINTER
(
id
));
g_hash_table_insert
(
jabber_cmds
,
protocol
,
commands
);
}
static
void
cmds_free_func
(
gpointer
value
)
{
GSList
*
commands
=
value
;
g_slist_free_full
(
commands
,
(
GDestroyNotify
)(
GCallback
)
purple_cmd_unregister
);
}
static
void
jabber_unregister_commands
(
PurpleProtocol
*
protocol
)
{
g_hash_table_remove
(
jabber_cmds
,
protocol
);
}
static
gboolean
find_acct_cb
(
PurpleAccount
*
account
,
const
gchar
*
protocol
)
{
const
gchar
*
account_protocol_id
=
NULL
;
account_protocol_id
=
purple_account_get_protocol_id
(
account
);
return
(
purple_strequal
(
protocol
,
account_protocol_id
)
&&
purple_account_is_connected
(
account
));
}
static
PurpleAccount
*
find_acct
(
const
char
*
protocol
,
const
char
*
acct_id
)
{
PurpleAccountManager
*
manager
=
NULL
;
PurpleAccount
*
acct
=
NULL
;
manager
=
purple_account_manager_get_default
();
/* If we have a specific acct, use it */
if
(
acct_id
)
{
acct
=
purple_account_manager_find
(
manager
,
acct_id
,
protocol
);
if
(
acct
&&
!
purple_account_is_connected
(
acct
))
{
acct
=
NULL
;
}
}
else
{
/* Otherwise find an active account for the protocol */
acct
=
purple_account_manager_find_custom
(
manager
,
(
GEqualFunc
)
find_acct_cb
,
protocol
);
}
return
acct
;
}
static
gboolean
xmpp_uri_handler
(
const
char
*
proto
,
const
char
*
user
,
GHashTable
*
params
,
gpointer
user_data
)
{
PurpleProtocol
*
protocol
=
(
PurpleProtocol
*
)
user_data
;
const
gchar
*
acct_id
=
NULL
;
PurpleAccount
*
acct
;
g_return_val_if_fail
(
PURPLE_IS_PROTOCOL
(
protocol
),
FALSE
);
if
(
g_ascii_strcasecmp
(
proto
,
"xmpp"
))
return
FALSE
;
if
(
params
!=
NULL
)
{
acct_id
=
g_hash_table_lookup
(
params
,
"account"
);
}
acct
=
find_acct
(
XMPP_PROTOCOL_ID
,
acct_id
);
if
(
!
acct
)
return
FALSE
;
/* xmpp:romeo@montague.net?message;subject=Test%20Message;body=Here%27s%20a%20test%20message */
/* params is NULL if the URI has no '?' (or anything after it) */
if
(
!
params
||
g_hash_table_lookup_extended
(
params
,
"message"
,
NULL
,
NULL
))
{
if
(
user
&&
*
user
)
{
PurpleConversation
*
im
=
purple_im_conversation_new
(
acct
,
user
);
const
gchar
*
body
=
NULL
;
purple_conversation_present
(
im
);
if
(
params
!=
NULL
)
{
body
=
g_hash_table_lookup
(
params
,
"body"
);
}
if
(
body
&&
*
body
)
purple_conversation_send_confirm
(
im
,
body
);
return
TRUE
;
}
}
else
if
(
g_hash_table_lookup_extended
(
params
,
"roster"
,
NULL
,
NULL
))
{
char
*
name
=
g_hash_table_lookup
(
params
,
"name"
);
if
(
user
&&
*
user
)
{
purple_blist_request_add_buddy
(
acct
,
user
,
NULL
,
name
);
return
TRUE
;
}
}
else
if
(
g_hash_table_lookup_extended
(
params
,
"join"
,
NULL
,
NULL
))
{
PurpleConnection
*
gc
=
purple_account_get_connection
(
acct
);
if
(
user
&&
*
user
)
{
GHashTable
*
params
=
jabber_chat_info_defaults
(
gc
,
user
);
jabber_chat_join
(
gc
,
params
);
}
return
TRUE
;
}
return
FALSE
;
}
static
void
jabber_do_init
(
void
)
{
PurpleUi
*
ui
=
purple_core_get_ui
();
const
gchar
*
ui_type
;
const
gchar
*
type
=
"pc"
;
/* default client type, if unknown or
unspecified */
const
gchar
*
ui_name
=
NULL
;
jabber_cmds
=
g_hash_table_new_full
(
g_direct_hash
,
g_direct_equal
,
NULL
,
cmds_free_func
);
ui_type
=
ui
?
purple_ui_get_client_type
(
ui
)
:
NULL
;
if
(
ui_type
)
{
if
(
purple_strequal
(
ui_type
,
"pc"
)
||
purple_strequal
(
ui_type
,
"console"
)
||
purple_strequal
(
ui_type
,
"phone"
)
||
purple_strequal
(
ui_type
,
"handheld"
)
||
purple_strequal
(
ui_type
,
"web"
)
||
purple_strequal
(
ui_type
,
"bot"
))
{
type
=
ui_type
;
}
}
if
(
ui
)
ui_name
=
purple_ui_get_name
(
ui
);
if
(
ui_name
==
NULL
)
ui_name
=
PACKAGE
;
jabber_add_identity
(
"client"
,
type
,
NULL
,
ui_name
);
/* initialize jabber_features list */
jabber_add_feature
(
NS_LAST_ACTIVITY
,
NULL
);
jabber_add_feature
(
NS_OOB_IQ_DATA
,
NULL
);
jabber_add_feature
(
NS_ENTITY_TIME
,
NULL
);
jabber_add_feature
(
"jabber:iq:version"
,
NULL
);
jabber_add_feature
(
"jabber:x:conference"
,
NULL
);
jabber_add_feature
(
NS_BYTESTREAMS
,
NULL
);
jabber_add_feature
(
"http://jabber.org/protocol/caps"
,
NULL
);
jabber_add_feature
(
"http://jabber.org/protocol/chatstates"
,
NULL
);
jabber_add_feature
(
NS_DISCO_INFO
,
NULL
);
jabber_add_feature
(
NS_DISCO_ITEMS
,
NULL
);
jabber_add_feature
(
NS_IBB
,
NULL
);
jabber_add_feature
(
"http://jabber.org/protocol/muc"
,
NULL
);
jabber_add_feature
(
"http://jabber.org/protocol/muc#user"
,
NULL
);
jabber_add_feature
(
"http://jabber.org/protocol/si"
,
NULL
);
jabber_add_feature
(
NS_SI_FILE_TRANSFER
,
NULL
);
jabber_add_feature
(
NS_XHTML_IM
,
NULL
);
jabber_add_feature
(
NS_PING
,
NULL
);
/* Bits Of Binary */
jabber_add_feature
(
NS_BOB
,
NULL
);
/* Jingle features! */
jabber_add_feature
(
JINGLE
,
NULL
);
jabber_add_feature
(
JINGLE_APP_RTP
,
NULL
);
jabber_add_feature
(
JINGLE_APP_RTP_SUPPORT_AUDIO
,
jabber_audio_enabled
);
jabber_add_feature
(
JINGLE_APP_RTP_SUPPORT_VIDEO
,
jabber_video_enabled
);
jabber_add_feature
(
JINGLE_TRANSPORT_RAWUDP
,
NULL
);
jabber_add_feature
(
JINGLE_TRANSPORT_ICEUDP
,
NULL
);
g_signal_connect
(
G_OBJECT
(
purple_media_manager_get
()),
"ui-caps-changed"
,
G_CALLBACK
(
jabber_caps_broadcast_change
),
NULL
);
/* reverse order of unload_plugin */
jabber_iq_init
();
jabber_presence_init
();
jabber_caps_init
();
/* PEP things should be init via jabber_pep_init, not here */
jabber_pep_init
();
jabber_data_init
();
jabber_bosh_init
();
/* TODO: Implement adding and retrieving own features via IPC API */
jabber_ibb_init
();
jabber_si_init
();
jabber_auth_init
();
}
static
void
jabber_do_uninit
(
void
)
{
/* reverse order of jabber_do_init */
jabber_bosh_uninit
();
jabber_data_uninit
();
jabber_si_uninit
();
jabber_ibb_uninit
();
/* PEP things should be uninit via jabber_pep_uninit, not here */
jabber_pep_uninit
();
jabber_caps_uninit
();
jabber_presence_uninit
();
jabber_iq_uninit
();
g_signal_handlers_disconnect_by_func
(
G_OBJECT
(
purple_media_manager_get
()),
G_CALLBACK
(
jabber_caps_broadcast_change
),
NULL
);
jabber_auth_uninit
();
g_clear_list
(
&
jabber_features
,
(
GDestroyNotify
)
jabber_feature_free
);
g_clear_list
(
&
jabber_identities
,
(
GDestroyNotify
)
jabber_identity_free
);
g_clear_pointer
(
&
jabber_cmds
,
g_hash_table_destroy
);
}
static
void
jabber_init_protocol
(
PurpleProtocol
*
protocol
)
{
++
plugin_ref
;
if
(
plugin_ref
==
1
)
jabber_do_init
();
jabber_register_commands
(
protocol
);
purple_signal_register
(
protocol
,
"jabber-register-namespace-watcher"
,
purple_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
G_TYPE_STRING
,
/* node */
G_TYPE_STRING
);
/* namespace */
purple_signal_register
(
protocol
,
"jabber-unregister-namespace-watcher"
,
purple_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
G_TYPE_STRING
,
/* node */
G_TYPE_STRING
);
/* namespace */
purple_signal_connect
(
protocol
,
"jabber-register-namespace-watcher"
,
protocol
,
G_CALLBACK
(
jabber_iq_signal_register
),
NULL
);
purple_signal_connect
(
protocol
,
"jabber-unregister-namespace-watcher"
,
protocol
,
G_CALLBACK
(
jabber_iq_signal_unregister
),
NULL
);
purple_signal_register
(
protocol
,
"jabber-receiving-xmlnode"
,
purple_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_POINTER
);
/* pointer to a PurpleXmlNode* */
purple_signal_register
(
protocol
,
"jabber-sending-xmlnode"
,
purple_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_POINTER
);
/* pointer to a PurpleXmlNode* */
/*
* Do not remove this or the plugin will fail. Completely. You have been
* warned!
*/
purple_signal_connect_priority
(
protocol
,
"jabber-sending-xmlnode"
,
protocol
,
G_CALLBACK
(
jabber_send_signal_cb
),
NULL
,
PURPLE_SIGNAL_PRIORITY_HIGHEST
);
purple_signal_register
(
protocol
,
"jabber-sending-text"
,
purple_marshal_VOID__POINTER_POINTER
,
G_TYPE_NONE
,
2
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_POINTER
);
/* pointer to a string */
purple_signal_register
(
protocol
,
"jabber-receiving-message"
,
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER_POINTER
,
G_TYPE_BOOLEAN
,
6
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_STRING
,
/* type */
G_TYPE_STRING
,
/* id */
G_TYPE_STRING
,
/* from */
G_TYPE_STRING
,
/* to */
PURPLE_TYPE_XMLNODE
);
purple_signal_register
(
protocol
,
"jabber-receiving-iq"
,
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
G_TYPE_BOOLEAN
,
5
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_STRING
,
/* type */
G_TYPE_STRING
,
/* id */
G_TYPE_STRING
,
/* from */
PURPLE_TYPE_XMLNODE
);
purple_signal_register
(
protocol
,
"jabber-watched-iq"
,
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER
,
G_TYPE_BOOLEAN
,
5
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_STRING
,
/* type */
G_TYPE_STRING
,
/* id */
G_TYPE_STRING
,
/* from */
PURPLE_TYPE_XMLNODE
);
/* child */
purple_signal_register
(
protocol
,
"jabber-receiving-presence"
,
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER
,
G_TYPE_BOOLEAN
,
4
,
PURPLE_TYPE_CONNECTION
,
G_TYPE_STRING
,
/* type */
G_TYPE_STRING
,
/* from */
PURPLE_TYPE_XMLNODE
);
}
static
void
jabber_uninit_protocol
(
PurpleProtocol
*
protocol
)
{
g_return_if_fail
(
plugin_ref
>
0
);
purple_signals_unregister_by_instance
(
protocol
);
jabber_unregister_commands
(
protocol
);
--
plugin_ref
;
if
(
plugin_ref
==
0
)
jabber_do_uninit
();
}
static
PurpleBuddyIconSpec
*
jabber_protocol_get_buddy_icon_spec
(
G_GNUC_UNUSED
PurpleProtocol
*
protocol
)
{
return
purple_buddy_icon_spec_new
(
"png"
,
32
,
32
,
96
,
96
,
0
,
PURPLE_ICON_SCALE_SEND
|
PURPLE_ICON_SCALE_DISPLAY
);
}
static
void
jabber_protocol_init
(
G_GNUC_UNUSED
JabberProtocol
*
self
)
{
}
static
void
jabber_protocol_class_init
(
JabberProtocolClass
*
klass
)
{
PurpleProtocolClass
*
protocol_class
=
PURPLE_PROTOCOL_CLASS
(
klass
);
protocol_class
->
get_buddy_icon_spec
=
jabber_protocol_get_buddy_icon_spec
;
protocol_class
->
login
=
jabber_login
;
protocol_class
->
close
=
jabber_close
;
protocol_class
->
status_types
=
jabber_status_types
;
}
static
void
jabber_protocol_class_finalize
(
G_GNUC_UNUSED
JabberProtocolClass
*
klass
)
{
}
static
void
xmpp_protocol_actions_iface_init
(
PurpleProtocolActionsInterface
*
iface
)
{
iface
->
get_prefix
=
xmpp_protocol_actions_get_prefix
;
iface
->
get_action_group
=
xmpp_protocol_actions_get_action_group
;
iface
->
get_menu
=
xmpp_protocol_actions_get_menu
;
}
static
void
jabber_protocol_client_iface_init
(
PurpleProtocolClientInterface
*
client_iface
)
{
client_iface
->
list_emblem
=
jabber_list_emblem
;
client_iface
->
blist_node_menu
=
jabber_blist_node_menu
;
client_iface
->
convo_closed
=
jabber_convo_closed
;
client_iface
->
normalize
=
jabber_client_normalize
;
client_iface
->
find_blist_chat
=
jabber_find_blist_chat
;
client_iface
->
offline_message
=
jabber_offline_message
;
}
static
void
jabber_protocol_server_iface_init
(
PurpleProtocolServerInterface
*
server_iface
)
{
server_iface
->
set_info
=
jabber_set_info
;
server_iface
->
get_info
=
jabber_buddy_get_info
;
server_iface
->
set_status
=
jabber_set_status
;
server_iface
->
set_idle
=
jabber_idle_set
;
server_iface
->
add_buddy
=
jabber_roster_add_buddy
;
server_iface
->
remove_buddy
=
jabber_roster_remove_buddy
;
server_iface
->
keepalive
=
jabber_keepalive
;
server_iface
->
get_keepalive_interval
=
jabber_get_keepalive_interval
;
server_iface
->
alias_buddy
=
jabber_roster_alias_change
;
server_iface
->
group_buddy
=
jabber_roster_group_change
;
server_iface
->
rename_group
=
jabber_roster_group_rename
;
server_iface
->
set_buddy_icon
=
jabber_set_buddy_icon
;
server_iface
->
send_raw
=
jabber_protocol_send_raw
;
}
static
void
jabber_protocol_im_iface_init
(
PurpleProtocolIMInterface
*
im_iface
)
{
im_iface
->
send
=
jabber_message_send_im
;
im_iface
->
send_typing
=
jabber_send_typing
;
}
static
GHashTable
*
jabber_protocol_chat_info_defaults
(
G_GNUC_UNUSED
PurpleProtocolChat
*
protocol_chat
,
PurpleConnection
*
connection
,
const
gchar
*
name
)
{
return
jabber_chat_info_defaults
(
connection
,
name
);
}
static
void
jabber_protocol_chat_join
(
G_GNUC_UNUSED
PurpleProtocolChat
*
protocol_chat
,
PurpleConnection
*
connection
,
GHashTable
*
components
)
{
jabber_chat_join
(
connection
,
components
);
}
static
void
jabber_protocol_chat_invite
(
G_GNUC_UNUSED
PurpleProtocolChat
*
protocol_chat
,
PurpleConnection
*
connection
,
gint
id
,
const
gchar
*
message
,
const
gchar
*
who
)
{
jabber_chat_invite
(
connection
,
id
,
message
,
who
);
}
static
void
jabber_protocol_chat_iface_init
(
PurpleProtocolChatInterface
*
chat_iface
)
{
chat_iface
->
info
=
jabber_chat_info
;
chat_iface
->
info_defaults
=
jabber_protocol_chat_info_defaults
;
chat_iface
->
join
=
jabber_protocol_chat_join
;
chat_iface
->
get_name
=
jabber_get_chat_name
;
chat_iface
->
invite
=
jabber_protocol_chat_invite
;
chat_iface
->
leave
=
jabber_chat_leave
;
chat_iface
->
send
=
jabber_message_send_chat
;
chat_iface
->
get_user_real_name
=
jabber_chat_user_real_name
;
chat_iface
->
set_topic
=
jabber_chat_set_topic
;
}
static
void
jabber_protocol_roomlist_iface_init
(
PurpleProtocolRoomlistInterface
*
roomlist_iface
)
{
roomlist_iface
->
get_list
=
jabber_roomlist_get_list
;
roomlist_iface
->
cancel
=
jabber_roomlist_cancel
;
roomlist_iface
->
room_serialize
=
jabber_roomlist_room_serialize
;
}
static
void
jabber_protocol_media_iface_init
(
PurpleProtocolMediaInterface
*
media_iface
)
{
media_iface
->
initiate_session
=
jabber_initiate_media
;
media_iface
->
get_caps
=
jabber_get_media_caps
;
}
static
void
jabber_protocol_xfer_iface_init
(
PurpleProtocolXferInterface
*
xfer_iface
)
{
xfer_iface
->
can_receive
=
jabber_can_receive_file
;
xfer_iface
->
send_file
=
jabber_si_xfer_send
;
xfer_iface
->
new_xfer
=
jabber_si_new_xfer
;
}
G_DEFINE_DYNAMIC_TYPE_EXTENDED
(
JabberProtocol
,
jabber_protocol
,
PURPLE_TYPE_PROTOCOL
,
G_TYPE_FLAG_ABSTRACT
,
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_ACTIONS
,
xmpp_protocol_actions_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_CLIENT
,
jabber_protocol_client_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_SERVER
,
jabber_protocol_server_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_IM
,
jabber_protocol_im_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_CHAT
,
jabber_protocol_chat_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_ROOMLIST
,
jabber_protocol_roomlist_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_MEDIA
,
jabber_protocol_media_iface_init
)
G_IMPLEMENT_INTERFACE_DYNAMIC
(
PURPLE_TYPE_PROTOCOL_XFER
,
jabber_protocol_xfer_iface_init
))
static
GPluginPluginInfo
*
jabber_query
(
G_GNUC_UNUSED
GError
**
error
)
{
return
purple_plugin_info_new
(
"id"
,
"prpl-xmpp"
,
"name"
,
"XMPP Protocols"
,
"version"
,
DISPLAY_VERSION
,
"category"
,
N_
(
"Protocol"
),
"summary"
,
N_
(
"XMPP Protocol Plugin"
),
"description"
,
N_
(
"XMPP Protocol Plugin"
),
"website"
,
PURPLE_WEBSITE
,
"abi-version"
,
PURPLE_ABI_VERSION
,
"flags"
,
PURPLE_PLUGIN_INFO_FLAGS_INTERNAL
|
PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD
,
NULL
);
}
static
gboolean
jabber_load
(
GPluginPlugin
*
plugin
,
GError
**
error
)
{
PurpleProtocolManager
*
manager
=
purple_protocol_manager_get_default
();
jingle_session_register
(
plugin
);
jingle_transport_register
(
plugin
);
jingle_iceudp_register
(
plugin
);
jingle_rawudp_register
(
plugin
);
jingle_content_register
(
plugin
);
jingle_rtp_register
(
plugin
);
jabber_protocol_register_type
(
G_TYPE_MODULE
(
plugin
));
xmpp_protocol_register
(
plugin
);
jabber_oob_xfer_register
(
G_TYPE_MODULE
(
plugin
));
jabber_si_xfer_register
(
G_TYPE_MODULE
(
plugin
));
xmpp_protocol
=
xmpp_protocol_new
();
if
(
!
purple_protocol_manager_register
(
manager
,
xmpp_protocol
,
error
))
{
g_clear_object
(
&
xmpp_protocol
);
return
FALSE
;
}
purple_signal_connect
(
purple_get_core
(),
"uri-handler"
,
xmpp_protocol
,
G_CALLBACK
(
xmpp_uri_handler
),
xmpp_protocol
);
jabber_init_protocol
(
xmpp_protocol
);
return
TRUE
;
}
static
gboolean
jabber_unload
(
G_GNUC_UNUSED
GPluginPlugin
*
plugin
,
G_GNUC_UNUSED
gboolean
shutdown
,
GError
**
error
)
{
PurpleProtocolManager
*
manager
=
purple_protocol_manager_get_default
();
if
(
!
purple_protocol_manager_unregister
(
manager
,
xmpp_protocol
,
error
))
{
return
FALSE
;
}
purple_signal_disconnect
(
purple_get_core
(),
"uri-handler"
,
xmpp_protocol
,
G_CALLBACK
(
xmpp_uri_handler
));
jabber_uninit_protocol
(
xmpp_protocol
);
g_clear_object
(
&
xmpp_protocol
);
return
TRUE
;
}
GPLUGIN_NATIVE_PLUGIN_DECLARE
(
jabber
)