pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Route GLib debug logging directly to the Finch debug window
2021-10-18, Elliott Sales de Andrade
1896a80ff8e3
Route GLib debug logging directly to the Finch debug window
Instead of flowing through purple debug, this merges some bits of the existing GLib log handler, and the purple debug printer.
Testing Done:
Open the Debug window an see some `GLib-*` outputs.
Reviewed at https://reviews.imfreedom.org/r/1057/
/*
* purple - Bonjour 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
<glib/gi18n-lib.h>
#include
<purple.h>
#include
<sys/types.h>
#include
<glib.h>
#ifdef HAVE_UNISTD_H
#include
<unistd.h>
#endif
#include
"xmpp.h"
#include
"parser.h"
#include
"bonjour.h"
#include
"buddy.h"
#include
"bonjour_ft.h"
#define STREAM_END "</stream:stream>"
/* TODO: specify version='1.0' and send stream features */
#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
"<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http:
//etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
enum
sent_stream_start_types
{
NOT_SENT
=
0
,
PARTIALLY_SENT
=
1
,
FULLY_SENT
=
2
};
static
void
xep_iq_parse
(
PurpleXmlNode
*
packet
,
PurpleBuddy
*
pb
);
static
BonjourXMPPConversation
*
bonjour_xmpp_conv_new
(
PurpleBuddy
*
pb
,
PurpleAccount
*
account
,
const
char
*
ip
)
{
BonjourXMPPConversation
*
bconv
=
g_new0
(
BonjourXMPPConversation
,
1
);
bconv
->
cancellable
=
g_cancellable_new
();
bconv
->
tx_buf
=
purple_circular_buffer_new
(
512
);
bconv
->
tx_handler
=
0
;
bconv
->
rx_handler
=
0
;
bconv
->
pb
=
pb
;
bconv
->
account
=
account
;
bconv
->
ip
=
g_strdup
(
ip
);
bonjour_parser_setup
(
bconv
);
return
bconv
;
}
static
const
char
*
_font_size_ichat_to_purple
(
int
size
)
{
if
(
size
>
24
)
{
return
"7"
;
}
else
if
(
size
>=
21
)
{
return
"6"
;
}
else
if
(
size
>=
17
)
{
return
"5"
;
}
else
if
(
size
>=
14
)
{
return
"4"
;
}
else
if
(
size
>=
12
)
{
return
"3"
;
}
else
if
(
size
>=
10
)
{
return
"2"
;
}
return
"1"
;
}
static
gchar
*
get_xmlnode_contents
(
PurpleXmlNode
*
node
)
{
gchar
*
contents
;
contents
=
purple_xmlnode_to_str
(
node
,
NULL
);
/* we just want the stuff inside <font></font>
* There isn't stuff exposed in PurpleXmlNode.c to do this more cleanly. */
if
(
contents
)
{
char
*
bodystart
=
strchr
(
contents
,
'>'
);
char
*
bodyend
=
bodystart
?
strrchr
(
bodystart
,
'<'
)
:
NULL
;
if
(
bodystart
&&
bodyend
&&
(
bodystart
+
1
)
!=
bodyend
)
{
*
bodyend
=
'\0'
;
memmove
(
contents
,
bodystart
+
1
,
(
bodyend
-
bodystart
));
}
}
return
contents
;
}
static
void
_xmpp_parse_and_write_message_to_ui
(
PurpleXmlNode
*
message_node
,
PurpleBuddy
*
pb
)
{
PurpleXmlNode
*
body_node
,
*
html_node
,
*
events_node
;
PurpleConnection
*
gc
=
purple_account_get_connection
(
purple_buddy_get_account
(
pb
));
gchar
*
body
=
NULL
;
body_node
=
purple_xmlnode_get_child
(
message_node
,
"body"
);
html_node
=
purple_xmlnode_get_child
(
message_node
,
"html"
);
if
(
body_node
==
NULL
&&
html_node
==
NULL
)
{
purple_debug_error
(
"bonjour"
,
"No body or html node found, discarding message.
\n
"
);
return
;
}
events_node
=
purple_xmlnode_get_child_with_namespace
(
message_node
,
"x"
,
"jabber:x:event"
);
if
(
events_node
!=
NULL
)
{
if
(
purple_xmlnode_get_child
(
events_node
,
"id"
)
!=
NULL
)
{
/* The user is just typing */
/* TODO: Deal with typing notification */
return
;
}
}
if
(
html_node
!=
NULL
)
{
PurpleXmlNode
*
html_body_node
;
html_body_node
=
purple_xmlnode_get_child
(
html_node
,
"body"
);
if
(
html_body_node
!=
NULL
)
{
PurpleXmlNode
*
html_body_font_node
;
html_body_font_node
=
purple_xmlnode_get_child
(
html_body_node
,
"font"
);
/* Types of messages sent by iChat */
if
(
html_body_font_node
!=
NULL
)
{
gchar
*
html_body
;
const
char
*
font_face
,
*
font_size
,
*
font_color
,
*
ichat_balloon_color
,
*
ichat_text_color
;
font_face
=
purple_xmlnode_get_attrib
(
html_body_font_node
,
"face"
);
/* The absolute iChat font sizes should be converted to 1..7 range */
font_size
=
purple_xmlnode_get_attrib
(
html_body_font_node
,
"ABSZ"
);
if
(
font_size
!=
NULL
)
font_size
=
_font_size_ichat_to_purple
(
atoi
(
font_size
));
font_color
=
purple_xmlnode_get_attrib
(
html_body_font_node
,
"color"
);
ichat_balloon_color
=
purple_xmlnode_get_attrib
(
html_body_node
,
"ichatballooncolor"
);
ichat_text_color
=
purple_xmlnode_get_attrib
(
html_body_node
,
"ichattextcolor"
);
html_body
=
get_xmlnode_contents
(
html_body_font_node
);
if
(
html_body
==
NULL
)
/* This is the kind of formatted messages that Purple creates */
html_body
=
purple_xmlnode_to_str
(
html_body_font_node
,
NULL
);
if
(
html_body
!=
NULL
)
{
GString
*
str
=
g_string_new
(
"<font"
);
if
(
font_face
)
g_string_append_printf
(
str
,
" face='%s'"
,
font_face
);
if
(
font_size
)
g_string_append_printf
(
str
,
" size='%s'"
,
font_size
);
if
(
font_color
)
g_string_append_printf
(
str
,
" color='%s'"
,
font_color
);
else
if
(
ichat_text_color
)
g_string_append_printf
(
str
,
" color='%s'"
,
ichat_text_color
);
if
(
ichat_balloon_color
)
g_string_append_printf
(
str
,
" back='%s'"
,
ichat_balloon_color
);
g_string_append_printf
(
str
,
">%s</font>"
,
html_body
);
body
=
g_string_free
(
str
,
FALSE
);
g_free
(
html_body
);
}
}
}
}
/* Compose the message */
if
(
body
==
NULL
&&
body_node
!=
NULL
)
body
=
purple_xmlnode_get_data
(
body_node
);
if
(
body
==
NULL
)
{
purple_debug_error
(
"bonjour"
,
"No html body or regular body found.
\n
"
);
return
;
}
/* Send the message to the UI */
purple_serv_got_im
(
gc
,
purple_buddy_get_name
(
pb
),
body
,
0
,
time
(
NULL
));
g_free
(
body
);
}
struct
_match_buddies_by_address
{
const
char
*
address
;
GSList
*
matched_buddies
;
};
static
void
_match_buddies_by_address
(
gpointer
value
,
gpointer
data
)
{
PurpleBuddy
*
pb
=
value
;
struct
_match_buddies_by_address
*
mbba
=
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
if
(
!
bb
)
{
return
;
}
/*
* If the current PurpleBuddy's data is not null, then continue to determine
* whether one of the buddies IPs matches the target IP.
*/
if
(
g_slist_find_custom
(
bb
->
ips
,
mbba
->
address
,
(
GCompareFunc
)
g_ascii_strcasecmp
))
{
mbba
->
matched_buddies
=
g_slist_prepend
(
mbba
->
matched_buddies
,
pb
);
}
}
static
GSList
*
_find_match_buddies_by_address
(
const
BonjourXMPP
*
jdata
,
const
char
*
address
)
{
struct
_match_buddies_by_address
mbba
=
{
.
address
=
address
,
.
matched_buddies
=
NULL
};
GSList
*
buddies
=
purple_blist_find_buddies
(
jdata
->
account
,
NULL
);
g_slist_foreach
(
buddies
,
_match_buddies_by_address
,
&
mbba
);
g_slist_free
(
buddies
);
return
mbba
.
matched_buddies
;
}
static
void
_send_data_write_cb
(
GObject
*
stream
,
gpointer
data
)
{
PurpleBuddy
*
pb
=
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
BonjourXMPPConversation
*
bconv
=
bb
->
conversation
;
gsize
writelen
;
gssize
ret
;
GError
*
error
=
NULL
;
writelen
=
purple_circular_buffer_get_max_read
(
bconv
->
tx_buf
);
if
(
writelen
==
0
)
{
g_source_remove
(
bconv
->
tx_handler
);
bconv
->
tx_handler
=
0
;
return
;
}
ret
=
g_pollable_output_stream_write_nonblocking
(
G_POLLABLE_OUTPUT_STREAM
(
stream
),
purple_circular_buffer_get_output
(
bconv
->
tx_buf
),
writelen
,
bconv
->
cancellable
,
&
error
);
if
(
ret
<
0
&&
error
->
code
==
G_IO_ERROR_WOULD_BLOCK
)
{
g_clear_error
(
&
error
);
return
;
}
else
if
(
ret
<=
0
)
{
PurpleAccount
*
account
=
NULL
;
PurpleConversation
*
conv
=
NULL
;
PurpleConversationManager
*
manager
=
NULL
;
manager
=
purple_conversation_manager_get_default
();
purple_debug_error
(
"bonjour"
,
"Error sending message to buddy %s error: %s"
,
purple_buddy_get_name
(
pb
),
error
?
error
->
message
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
conv
=
purple_conversation_manager_find_im
(
manager
,
account
,
bb
->
name
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send message."
),
PURPLE_MESSAGE_ERROR
);
}
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
g_clear_error
(
&
error
);
return
;
}
purple_circular_buffer_mark_read
(
bconv
->
tx_buf
,
ret
);
}
static
gint
_send_data
(
PurpleBuddy
*
pb
,
char
*
message
)
{
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
BonjourXMPPConversation
*
bconv
=
bb
->
conversation
;
gsize
len
=
strlen
(
message
);
gssize
ret
;
GError
*
error
=
NULL
;
/* If we're not ready to actually send, append it to the buffer */
if
(
bconv
->
tx_handler
!=
0
||
bconv
->
sent_stream_start
!=
FULLY_SENT
||
!
bconv
->
recv_stream_start
||
purple_circular_buffer_get_max_read
(
bconv
->
tx_buf
)
>
0
)
{
ret
=
-1
;
g_set_error_literal
(
&
error
,
G_IO_ERROR
,
G_IO_ERROR_WOULD_BLOCK
,
"Not yet ready to send."
);
}
else
{
ret
=
g_pollable_output_stream_write_nonblocking
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
message
,
len
,
bconv
->
cancellable
,
&
error
);
}
if
(
ret
==
-1
&&
error
->
code
==
G_IO_ERROR_WOULD_BLOCK
)
{
ret
=
0
;
g_clear_error
(
&
error
);
}
else
if
(
ret
<=
0
)
{
PurpleAccount
*
account
;
PurpleConversation
*
conv
;
PurpleConversationManager
*
manager
;
manager
=
purple_conversation_manager_get_default
();
purple_debug_error
(
"bonjour"
,
"Error sending message to buddy %s error: %s"
,
purple_buddy_get_name
(
pb
),
error
?
error
->
message
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
conv
=
purple_conversation_manager_find_im
(
manager
,
account
,
bb
->
name
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send message."
),
PURPLE_MESSAGE_ERROR
);
}
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
g_clear_error
(
&
error
);
return
-1
;
}
if
(
ret
<
len
)
{
/* Don't interfere with the stream starting */
if
(
bconv
->
sent_stream_start
==
FULLY_SENT
&&
bconv
->
recv_stream_start
&&
bconv
->
tx_handler
==
0
)
{
GSource
*
source
=
g_pollable_output_stream_create_source
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
bconv
->
cancellable
);
g_source_set_callback
(
source
,
(
GSourceFunc
)
_send_data_write_cb
,
pb
,
NULL
);
bconv
->
tx_handler
=
g_source_attach
(
source
,
NULL
);
g_source_unref
(
source
);
}
purple_circular_buffer_append
(
bconv
->
tx_buf
,
message
+
ret
,
len
-
ret
);
}
return
ret
;
}
void
bonjour_xmpp_process_packet
(
PurpleBuddy
*
pb
,
PurpleXmlNode
*
packet
)
{
g_return_if_fail
(
packet
!=
NULL
);
g_return_if_fail
(
pb
!=
NULL
);
if
(
purple_strequal
(
packet
->
name
,
"message"
))
_xmpp_parse_and_write_message_to_ui
(
packet
,
pb
);
else
if
(
purple_strequal
(
packet
->
name
,
"iq"
))
xep_iq_parse
(
packet
,
pb
);
else
{
purple_debug_warning
(
"bonjour"
,
"Unknown packet: %s
\n
"
,
packet
->
name
?
packet
->
name
:
"(null)"
);
}
}
static
void
bonjour_xmpp_stream_ended
(
BonjourXMPPConversation
*
bconv
)
{
/* Inform the user that the conversation has been closed */
BonjourBuddy
*
bb
=
NULL
;
const
gchar
*
name
=
bconv
->
pb
?
purple_buddy_get_name
(
bconv
->
pb
)
:
"(unknown)"
;
purple_debug_info
(
"bonjour"
,
"Received conversation close notification from %s.
\n
"
,
name
);
if
(
bconv
->
pb
!=
NULL
)
bb
=
purple_buddy_get_protocol_data
(
bconv
->
pb
);
/* Close the socket, clear the watcher and free memory */
bonjour_xmpp_close_conversation
(
bconv
);
if
(
bb
)
bb
->
conversation
=
NULL
;
}
static
gboolean
_client_socket_handler
(
GObject
*
stream
,
gpointer
data
)
{
BonjourXMPPConversation
*
bconv
=
data
;
GError
*
error
=
NULL
;
gssize
len
;
static
char
message
[
4096
];
/* Read the data from the socket */
len
=
g_pollable_input_stream_read_nonblocking
(
G_POLLABLE_INPUT_STREAM
(
stream
),
message
,
sizeof
(
message
)
-
1
,
bconv
->
cancellable
,
&
error
);
if
(
len
==
-1
)
{
/* There has been an error reading from the socket */
if
(
error
==
NULL
||
(
error
->
code
!=
G_IO_ERROR_WOULD_BLOCK
&&
error
->
code
!=
G_IO_ERROR_CANCELLED
))
{
purple_debug_warning
(
"bonjour"
,
"receive of %"
G_GSSIZE_FORMAT
" error: %s"
,
len
,
error
?
error
->
message
:
"(null)"
);
bonjour_xmpp_close_conversation
(
bconv
);
if
(
bconv
->
pb
!=
NULL
)
{
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
bconv
->
pb
);
if
(
bb
!=
NULL
)
bb
->
conversation
=
NULL
;
}
/* I guess we really don't need to notify the user.
* If they try to send another message it'll reconnect */
}
g_clear_error
(
&
error
);
return
FALSE
;
}
else
if
(
len
==
0
)
{
/* The other end has closed the socket */
const
gchar
*
name
=
purple_buddy_get_name
(
bconv
->
pb
);
purple_debug_warning
(
"bonjour"
,
"Connection closed (without stream end) by %s.
\n
"
,
(
name
)
?
name
:
"(unknown)"
);
bonjour_xmpp_stream_ended
(
bconv
);
return
FALSE
;
}
message
[
len
]
=
'\0'
;
purple_debug_info
(
"bonjour"
,
"Receive: -%s- %"
G_GSSIZE_FORMAT
" bytes
\n
"
,
message
,
len
);
bonjour_parser_process
(
bconv
,
message
,
len
);
return
TRUE
;
}
struct
_stream_start_data
{
char
*
msg
;
};
static
void
_start_stream
(
GObject
*
stream
,
gpointer
data
)
{
BonjourXMPPConversation
*
bconv
=
data
;
struct
_stream_start_data
*
ss
=
bconv
->
stream_data
;
GError
*
error
=
NULL
;
gsize
len
;
gssize
ret
;
len
=
strlen
(
ss
->
msg
);
/* Start Stream */
ret
=
g_pollable_output_stream_write_nonblocking
(
G_POLLABLE_OUTPUT_STREAM
(
stream
),
ss
->
msg
,
len
,
bconv
->
cancellable
,
&
error
);
if
(
ret
==
-1
&&
error
->
code
==
G_IO_ERROR_WOULD_BLOCK
)
{
g_clear_error
(
&
error
);
return
;
}
else
if
(
ret
<=
0
)
{
PurpleConversation
*
conv
;
PurpleConversationManager
*
manager
;
BonjourBuddy
*
bb
=
NULL
;
const
char
*
bname
=
bconv
->
buddy_name
;
manager
=
purple_conversation_manager_get_default
();
if
(
bconv
->
pb
)
{
bb
=
purple_buddy_get_protocol_data
(
bconv
->
pb
);
bname
=
purple_buddy_get_name
(
bconv
->
pb
);
}
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at %s error: %s"
,
bname
?
bname
:
"(unknown)"
,
bconv
->
ip
,
error
?
error
->
message
:
"(null)"
);
conv
=
purple_conversation_manager_find_im
(
manager
,
bconv
->
account
,
bname
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_ERROR
);
}
bonjour_xmpp_close_conversation
(
bconv
);
if
(
bb
!=
NULL
)
{
bb
->
conversation
=
NULL
;
}
g_clear_error
(
&
error
);
return
;
}
/* This is EXTREMELY unlikely to happen */
if
(
G_UNLIKELY
(
ret
<
len
))
{
char
*
tmp
=
g_strdup
(
ss
->
msg
+
ret
);
g_free
(
ss
->
msg
);
ss
->
msg
=
tmp
;
return
;
}
g_free
(
ss
->
msg
);
g_free
(
ss
);
bconv
->
stream_data
=
NULL
;
/* Stream started; process the send buffer if there is one */
g_source_remove
(
bconv
->
tx_handler
);
bconv
->
tx_handler
=
0
;
bconv
->
sent_stream_start
=
FULLY_SENT
;
bonjour_xmpp_stream_started
(
bconv
);
}
static
gboolean
bonjour_xmpp_send_stream_init
(
BonjourXMPPConversation
*
bconv
,
GError
**
error
)
{
gchar
*
stream_start
;
gsize
len
;
gssize
ret
;
const
char
*
bname
=
bconv
->
buddy_name
;
g_return_val_if_fail
(
error
!=
NULL
,
FALSE
);
if
(
bconv
->
pb
!=
NULL
)
bname
=
purple_buddy_get_name
(
bconv
->
pb
);
/* If we have no idea who "to" is, use an empty string.
* If we don't know now, it is because the other side isn't playing nice, so they can't complain. */
if
(
bname
==
NULL
)
bname
=
""
;
stream_start
=
g_strdup_printf
(
DOCTYPE
,
bonjour_get_jid
(
bconv
->
account
),
bname
);
len
=
strlen
(
stream_start
);
bconv
->
sent_stream_start
=
PARTIALLY_SENT
;
/* Start the stream */
ret
=
g_pollable_output_stream_write_nonblocking
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
stream_start
,
len
,
bconv
->
cancellable
,
error
);
if
(
ret
==
-1
&&
(
*
error
)
->
code
==
G_IO_ERROR_WOULD_BLOCK
)
{
ret
=
0
;
g_clear_error
(
error
);
}
else
if
(
ret
<=
0
)
{
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at %s error: %s"
,
(
*
bname
)
?
bname
:
"(unknown)"
,
bconv
->
ip
,
*
error
?
(
*
error
)
->
message
:
"(null)"
);
if
(
bconv
->
pb
)
{
PurpleConversation
*
conv
;
PurpleConversationManager
*
manager
;
manager
=
purple_conversation_manager_get_default
();
conv
=
purple_conversation_manager_find_im
(
manager
,
bconv
->
account
,
bname
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_ERROR
);
}
}
purple_gio_graceful_close
(
G_IO_STREAM
(
bconv
->
socket
),
G_INPUT_STREAM
(
bconv
->
input
),
G_OUTPUT_STREAM
(
bconv
->
output
));
g_clear_object
(
&
bconv
->
socket
);
g_clear_object
(
&
bconv
->
input
);
g_clear_object
(
&
bconv
->
output
);
g_free
(
stream_start
);
return
FALSE
;
}
/* This is unlikely to happen */
if
(
ret
<
len
)
{
GSource
*
source
;
struct
_stream_start_data
*
ss
=
g_new
(
struct
_stream_start_data
,
1
);
ss
->
msg
=
g_strdup
(
stream_start
+
ret
);
bconv
->
stream_data
=
ss
;
/* Finish sending the stream start */
source
=
g_pollable_output_stream_create_source
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
bconv
->
cancellable
);
g_source_set_callback
(
source
,
(
GSourceFunc
)
_start_stream
,
bconv
,
NULL
);
bconv
->
tx_handler
=
g_source_attach
(
source
,
NULL
);
g_source_unref
(
source
);
}
else
{
bconv
->
sent_stream_start
=
FULLY_SENT
;
}
g_free
(
stream_start
);
return
TRUE
;
}
/* This gets called when we've successfully sent our <stream:stream />
* AND when we've received a <stream:stream /> */
void
bonjour_xmpp_stream_started
(
BonjourXMPPConversation
*
bconv
)
{
GError
*
error
=
NULL
;
if
(
bconv
->
sent_stream_start
==
NOT_SENT
&&
!
bonjour_xmpp_send_stream_init
(
bconv
,
&
error
))
{
const
char
*
bname
=
bconv
->
buddy_name
;
if
(
bconv
->
pb
)
bname
=
purple_buddy_get_name
(
bconv
->
pb
);
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at %s error: %s"
,
bname
?
bname
:
"(unknown)"
,
bconv
->
ip
,
error
?
error
->
message
:
"(null)"
);
if
(
bconv
->
pb
)
{
PurpleConversation
*
conv
;
PurpleConversationManager
*
manager
;
manager
=
purple_conversation_manager_get_default
();
conv
=
purple_conversation_manager_find_im
(
manager
,
bconv
->
account
,
bname
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_ERROR
);
}
}
/* We don't want to receive anything else */
purple_gio_graceful_close
(
G_IO_STREAM
(
bconv
->
socket
),
G_INPUT_STREAM
(
bconv
->
input
),
G_OUTPUT_STREAM
(
bconv
->
output
));
g_clear_object
(
&
bconv
->
socket
);
g_clear_object
(
&
bconv
->
input
);
g_clear_object
(
&
bconv
->
output
);
/* This must be asynchronous because it destroys the parser and we
* may be in the middle of parsing.
*/
async_bonjour_xmpp_close_conversation
(
bconv
);
g_clear_error
(
&
error
);
return
;
}
/* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */
/* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */
if
(
bconv
->
sent_stream_start
==
FULLY_SENT
&&
bconv
->
recv_stream_start
&&
bconv
->
pb
&&
purple_circular_buffer_get_max_read
(
bconv
->
tx_buf
)
>
0
)
{
/* Watch for when we can write the buffered messages */
GSource
*
source
=
g_pollable_output_stream_create_source
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
bconv
->
cancellable
);
g_source_set_callback
(
source
,
(
GSourceFunc
)
_send_data_write_cb
,
bconv
->
pb
,
NULL
);
bconv
->
tx_handler
=
g_source_attach
(
source
,
NULL
);
g_source_unref
(
source
);
/* We can probably write the data right now. */
_send_data_write_cb
(
G_OBJECT
(
bconv
->
output
),
bconv
->
pb
);
}
}
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
static
void
_server_socket_handler
(
GSocketService
*
service
,
GSocketConnection
*
connection
,
GObject
*
source_object
,
gpointer
data
)
{
BonjourXMPP
*
jdata
=
data
;
GSocketAddress
*
their_addr
;
/* connector's address information */
GInetAddress
*
their_inet_addr
;
gchar
*
address_text
;
BonjourXMPPConversation
*
bconv
;
GSList
*
buddies
;
GSource
*
source
;
their_addr
=
g_socket_connection_get_remote_address
(
connection
,
NULL
);
if
(
their_addr
==
NULL
)
{
return
;
}
their_inet_addr
=
g_inet_socket_address_get_address
(
G_INET_SOCKET_ADDRESS
(
their_addr
));
/* Look for the buddy that has opened the conversation and fill information */
address_text
=
g_inet_address_to_string
(
their_inet_addr
);
if
(
g_inet_address_get_family
(
their_inet_addr
)
==
G_SOCKET_FAMILY_IPV6
&&
g_inet_address_get_is_link_local
(
their_inet_addr
))
{
gchar
*
tmp
=
g_strdup_printf
(
"%s%%%d"
,
address_text
,
g_inet_socket_address_get_scope_id
(
G_INET_SOCKET_ADDRESS
(
their_addr
)));
g_free
(
address_text
);
address_text
=
tmp
;
}
g_object_unref
(
their_addr
);
purple_debug_info
(
"bonjour"
,
"Received incoming connection from %s.
\n
"
,
address_text
);
buddies
=
_find_match_buddies_by_address
(
jdata
,
address_text
);
if
(
buddies
==
NULL
)
{
purple_debug_info
(
"bonjour"
,
"We don't like invisible buddies, this is not a superheroes comic
\n
"
);
g_free
(
address_text
);
return
;
}
g_slist_free
(
buddies
);
/* We've established that this *could* be from one of our buddies.
* Wait for the stream open to see if that matches too before assigning it.
*/
bconv
=
bonjour_xmpp_conv_new
(
NULL
,
jdata
->
account
,
address_text
);
/* We wait for the stream start before doing anything else */
bconv
->
socket
=
g_object_ref
(
connection
);
bconv
->
input
=
g_object_ref
(
g_io_stream_get_input_stream
(
G_IO_STREAM
(
bconv
->
socket
)));
bconv
->
output
=
g_object_ref
(
g_io_stream_get_output_stream
(
G_IO_STREAM
(
bconv
->
socket
)));
source
=
g_pollable_input_stream_create_source
(
G_POLLABLE_INPUT_STREAM
(
bconv
->
input
),
bconv
->
cancellable
);
g_source_set_callback
(
source
,
(
GSourceFunc
)
_client_socket_handler
,
bconv
,
NULL
);
bconv
->
rx_handler
=
g_source_attach
(
source
,
NULL
);
g_source_unref
(
source
);
g_free
(
address_text
);
}
gint
bonjour_xmpp_start
(
BonjourXMPP
*
jdata
)
{
GError
*
error
=
NULL
;
guint16
port
;
purple_debug_info
(
"bonjour"
,
"Attempting to bind IP socket to port %d."
,
jdata
->
port
);
/* Open a listening server for incoming conversations */
jdata
->
service
=
g_socket_service_new
();
g_socket_listener_set_backlog
(
G_SOCKET_LISTENER
(
jdata
->
service
),
10
);
port
=
jdata
->
port
;
if
(
!
g_socket_listener_add_inet_port
(
G_SOCKET_LISTENER
(
jdata
->
service
),
port
,
NULL
,
&
error
))
{
purple_debug_info
(
"bonjour"
,
"Unable to bind to specified port %i: %s"
,
port
,
error
?
error
->
message
:
"(unknown)"
);
g_clear_error
(
&
error
);
port
=
g_socket_listener_add_any_inet_port
(
G_SOCKET_LISTENER
(
jdata
->
service
),
NULL
,
&
error
);
if
(
port
==
0
)
{
purple_debug_error
(
"bonjour"
,
"Unable to create socket: %s"
,
error
?
error
->
message
:
"(unknown)"
);
g_clear_error
(
&
error
);
return
-1
;
}
}
purple_debug_info
(
"bonjour"
,
"Bound IP socket to port %u."
,
port
);
jdata
->
port
=
port
;
g_signal_connect
(
G_OBJECT
(
jdata
->
service
),
"incoming"
,
G_CALLBACK
(
_server_socket_handler
),
jdata
);
return
jdata
->
port
;
}
static
void
_connected_to_buddy
(
GObject
*
source
,
GAsyncResult
*
res
,
gpointer
user_data
)
{
PurpleBuddy
*
pb
=
user_data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
GSocketConnection
*
conn
;
GSource
*
rx_source
;
GError
*
error
=
NULL
;
conn
=
g_socket_client_connect_to_host_finish
(
G_SOCKET_CLIENT
(
source
),
res
,
&
error
);
if
(
conn
==
NULL
)
{
PurpleAccount
*
account
=
NULL
;
PurpleConversation
*
conv
=
NULL
;
PurpleConversationManager
*
manager
=
NULL
;
GSList
*
tmp
;
if
(
error
&&
error
->
code
==
G_IO_ERROR_CANCELLED
)
{
/* This conversation was closed before it started. */
g_error_free
(
error
);
return
;
}
purple_debug_error
(
"bonjour"
,
"Error connecting to buddy %s at %s:%d "
"(%s); Trying next IP address"
,
purple_buddy_get_name
(
pb
),
bb
->
conversation
->
ip
,
bb
->
port_p2pj
,
error
?
error
->
message
:
"(unknown)"
);
g_clear_error
(
&
error
);
/* There may be multiple entries for the same IP - one per
* presence received (e.g. multiple interfaces).
* We need to make sure that we find the previously used entry.
*/
tmp
=
g_slist_find
(
bb
->
ips
,
bb
->
conversation
->
ip_link
);
if
(
tmp
)
tmp
=
g_slist_next
(
tmp
);
account
=
purple_buddy_get_account
(
pb
);
if
(
tmp
!=
NULL
)
{
const
gchar
*
ip
;
GSocketClient
*
client
;
bb
->
conversation
->
ip_link
=
ip
=
tmp
->
data
;
purple_debug_info
(
"bonjour"
,
"Starting conversation with %s at %s:%d
\n
"
,
purple_buddy_get_name
(
pb
),
ip
,
bb
->
port_p2pj
);
/* Make sure to connect without a proxy. */
client
=
g_socket_client_new
();
if
(
client
!=
NULL
)
{
g_free
(
bb
->
conversation
->
ip
);
bb
->
conversation
->
ip
=
g_strdup
(
ip
);
g_socket_client_connect_to_host_async
(
client
,
ip
,
bb
->
port_p2pj
,
bb
->
conversation
->
cancellable
,
_connected_to_buddy
,
pb
);
g_object_unref
(
client
);
return
;
}
}
purple_debug_error
(
"bonjour"
,
"No more addresses for buddy %s. Aborting"
,
purple_buddy_get_name
(
pb
));
manager
=
purple_conversation_manager_get_default
();
conv
=
purple_conversation_manager_find_im
(
manager
,
account
,
bb
->
name
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_ERROR
);
}
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
return
;
}
bb
->
conversation
->
socket
=
conn
;
bb
->
conversation
->
input
=
g_object_ref
(
g_io_stream_get_input_stream
(
G_IO_STREAM
(
conn
)));
bb
->
conversation
->
output
=
g_object_ref
(
g_io_stream_get_output_stream
(
G_IO_STREAM
(
conn
)));
if
(
!
bonjour_xmpp_send_stream_init
(
bb
->
conversation
,
&
error
))
{
PurpleAccount
*
account
=
NULL
;
PurpleConversation
*
conv
=
NULL
;
PurpleConversationManager
*
manager
=
NULL
;
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at "
"%s:%d error: %s"
,
purple_buddy_get_name
(
pb
),
bb
->
conversation
->
ip
,
bb
->
port_p2pj
,
error
?
error
->
message
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
manager
=
purple_conversation_manager_get_default
();
conv
=
purple_conversation_manager_find_im
(
manager
,
account
,
bb
->
name
);
if
(
conv
!=
NULL
)
{
purple_conversation_write_system_message
(
conv
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_ERROR
);
}
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
g_clear_error
(
&
error
);
return
;
}
/* Start listening for the stream acknowledgement */
rx_source
=
g_pollable_input_stream_create_source
(
G_POLLABLE_INPUT_STREAM
(
bb
->
conversation
->
input
),
bb
->
conversation
->
cancellable
);
g_source_set_callback
(
rx_source
,
(
GSourceFunc
)
_client_socket_handler
,
bb
->
conversation
,
NULL
);
bb
->
conversation
->
rx_handler
=
g_source_attach
(
rx_source
,
NULL
);
g_source_unref
(
rx_source
);
}
void
bonjour_xmpp_conv_match_by_name
(
BonjourXMPPConversation
*
bconv
)
{
PurpleBuddy
*
pb
=
NULL
;
BonjourBuddy
*
bb
=
NULL
;
g_return_if_fail
(
bconv
->
ip
!=
NULL
);
g_return_if_fail
(
bconv
->
pb
==
NULL
);
pb
=
purple_blist_find_buddy
(
bconv
->
account
,
bconv
->
buddy_name
);
if
(
pb
&&
(
bb
=
purple_buddy_get_protocol_data
(
pb
)))
{
purple_debug_info
(
"bonjour"
,
"Found buddy %s for incoming conversation
\"
from
\"
attrib.
\n
"
,
purple_buddy_get_name
(
pb
));
/* Check that one of the buddy's IPs matches */
if
(
g_slist_find_custom
(
bb
->
ips
,
bconv
->
ip
,
(
GCompareFunc
)
g_ascii_strcasecmp
))
{
PurpleConnection
*
pc
=
purple_account_get_connection
(
bconv
->
account
);
BonjourData
*
bd
=
purple_connection_get_protocol_data
(
pc
);
BonjourXMPP
*
jdata
=
bd
->
xmpp_data
;
purple_debug_info
(
"bonjour"
,
"Matched buddy %s to incoming conversation
\"
from
\"
attrib and IP (%s)"
,
purple_buddy_get_name
(
pb
),
bconv
->
ip
);
/* Attach conv. to buddy and remove from pending list */
jdata
->
pending_conversations
=
g_slist_remove
(
jdata
->
pending_conversations
,
bconv
);
/* Check if the buddy already has a conversation and, if so, replace it */
if
(
bb
->
conversation
!=
NULL
&&
bb
->
conversation
!=
bconv
)
{
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
}
bconv
->
pb
=
pb
;
bb
->
conversation
=
bconv
;
}
}
/* We've failed to match a buddy - give up */
if
(
bconv
->
pb
==
NULL
)
{
/* This must be asynchronous because it destroys the parser and we
* may be in the middle of parsing.
*/
async_bonjour_xmpp_close_conversation
(
bconv
);
}
}
void
bonjour_xmpp_conv_match_by_ip
(
BonjourXMPPConversation
*
bconv
)
{
PurpleConnection
*
pc
=
purple_account_get_connection
(
bconv
->
account
);
BonjourData
*
bd
=
purple_connection_get_protocol_data
(
pc
);
BonjourXMPP
*
jdata
=
bd
->
xmpp_data
;
GSList
*
buddies
;
buddies
=
_find_match_buddies_by_address
(
jdata
,
bconv
->
ip
);
/* If there is exactly one match, use it */
if
(
!
buddies
)
{
purple_debug_error
(
"bonjour"
,
"No buddies matched for ip %s."
,
bconv
->
ip
);
}
else
if
(
buddies
->
next
!=
NULL
)
{
purple_debug_error
(
"bonjour"
,
"More than one buddy matched for ip %s."
,
bconv
->
ip
);
}
else
{
PurpleBuddy
*
pb
=
buddies
->
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
purple_debug_info
(
"bonjour"
,
"Matched buddy %s to incoming conversation using IP (%s)"
,
purple_buddy_get_name
(
pb
),
bconv
->
ip
);
/* Attach conv. to buddy and remove from pending list */
jdata
->
pending_conversations
=
g_slist_remove
(
jdata
->
pending_conversations
,
bconv
);
/* Check if the buddy already has a conversation and, if so, replace it */
if
(
bb
->
conversation
!=
NULL
&&
bb
->
conversation
!=
bconv
)
{
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
}
bconv
->
pb
=
pb
;
bb
->
conversation
=
bconv
;
}
/* We've failed to match a buddy - give up */
if
(
bconv
->
pb
==
NULL
)
{
/* This must be asynchronous because it destroys the parser and we
* may be in the middle of parsing.
*/
async_bonjour_xmpp_close_conversation
(
bconv
);
}
g_slist_free
(
buddies
);
}
static
PurpleBuddy
*
_find_or_start_conversation
(
BonjourXMPP
*
jdata
,
const
gchar
*
to
)
{
PurpleBuddy
*
pb
=
NULL
;
BonjourBuddy
*
bb
=
NULL
;
g_return_val_if_fail
(
jdata
!=
NULL
,
NULL
);
g_return_val_if_fail
(
to
!=
NULL
,
NULL
);
pb
=
purple_blist_find_buddy
(
jdata
->
account
,
to
);
if
(
pb
==
NULL
||
(
bb
=
purple_buddy_get_protocol_data
(
pb
))
==
NULL
)
/* You can not send a message to an offline buddy */
return
NULL
;
/* Check if there is a previously open conversation */
if
(
bb
->
conversation
==
NULL
)
{
GSocketClient
*
client
;
/* Start with the first IP address. */
const
gchar
*
ip
=
bb
->
ips
->
data
;
purple_debug_info
(
"bonjour"
,
"Starting conversation with %s at %s:%d"
,
to
,
ip
,
bb
->
port_p2pj
);
/* Make sure to connect without a proxy. */
client
=
g_socket_client_new
();
if
(
client
==
NULL
)
{
purple_debug_error
(
"bonjour"
,
"Unable to connect to buddy (%s)."
,
to
);
return
NULL
;
}
bb
->
conversation
=
bonjour_xmpp_conv_new
(
pb
,
jdata
->
account
,
ip
);
bb
->
conversation
->
ip_link
=
ip
;
g_socket_client_connect_to_host_async
(
client
,
ip
,
bb
->
port_p2pj
,
bb
->
conversation
->
cancellable
,
_connected_to_buddy
,
pb
);
g_object_unref
(
client
);
}
return
pb
;
}
int
bonjour_xmpp_send_message
(
BonjourXMPP
*
jdata
,
const
gchar
*
to
,
const
gchar
*
body
)
{
PurpleXmlNode
*
message_node
,
*
node
,
*
node2
;
gchar
*
message
,
*
xhtml
;
PurpleBuddy
*
pb
;
BonjourBuddy
*
bb
;
int
ret
;
pb
=
_find_or_start_conversation
(
jdata
,
to
);
if
(
pb
==
NULL
||
(
bb
=
purple_buddy_get_protocol_data
(
pb
))
==
NULL
)
{
purple_debug_info
(
"bonjour"
,
"Can't send a message to an offline buddy (%s).
\n
"
,
to
);
/* You can not send a message to an offline buddy */
return
-10000
;
}
purple_markup_html_to_xhtml
(
body
,
&
xhtml
,
&
message
);
message_node
=
purple_xmlnode_new
(
"message"
);
purple_xmlnode_set_attrib
(
message_node
,
"to"
,
bb
->
name
);
purple_xmlnode_set_attrib
(
message_node
,
"from"
,
bonjour_get_jid
(
jdata
->
account
));
purple_xmlnode_set_attrib
(
message_node
,
"type"
,
"chat"
);
/* Enclose the message from the UI within a "font" node */
node
=
purple_xmlnode_new_child
(
message_node
,
"body"
);
purple_xmlnode_insert_data
(
node
,
message
,
strlen
(
message
));
g_free
(
message
);
node
=
purple_xmlnode_new_child
(
message_node
,
"html"
);
purple_xmlnode_set_namespace
(
node
,
"http://www.w3.org/1999/xhtml"
);
node
=
purple_xmlnode_new_child
(
node
,
"body"
);
message
=
g_strdup_printf
(
"<font>%s</font>"
,
xhtml
);
node2
=
purple_xmlnode_from_str
(
message
,
strlen
(
message
));
g_free
(
xhtml
);
g_free
(
message
);
purple_xmlnode_insert_child
(
node
,
node2
);
node
=
purple_xmlnode_new_child
(
message_node
,
"x"
);
purple_xmlnode_set_namespace
(
node
,
"jabber:x:event"
);
purple_xmlnode_insert_child
(
node
,
purple_xmlnode_new
(
"composing"
));
message
=
purple_xmlnode_to_str
(
message_node
,
NULL
);
purple_xmlnode_free
(
message_node
);
ret
=
_send_data
(
pb
,
message
)
>=
0
;
g_free
(
message
);
return
ret
;
}
static
gboolean
_async_bonjour_xmpp_close_conversation_cb
(
gpointer
data
)
{
BonjourXMPPConversation
*
bconv
=
data
;
bonjour_xmpp_close_conversation
(
bconv
);
return
FALSE
;
}
void
async_bonjour_xmpp_close_conversation
(
BonjourXMPPConversation
*
bconv
)
{
PurpleConnection
*
pc
=
purple_account_get_connection
(
bconv
->
account
);
BonjourData
*
bd
=
purple_connection_get_protocol_data
(
pc
);
BonjourXMPP
*
jdata
=
bd
->
xmpp_data
;
jdata
->
pending_conversations
=
g_slist_remove
(
jdata
->
pending_conversations
,
bconv
);
/* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/
if
(
bconv
->
pb
!=
NULL
)
{
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
bconv
->
pb
);
if
(
bb
->
conversation
==
bconv
)
bb
->
conversation
=
NULL
;
}
bconv
->
close_timeout
=
g_timeout_add
(
0
,
_async_bonjour_xmpp_close_conversation_cb
,
bconv
);
}
void
bonjour_xmpp_close_conversation
(
BonjourXMPPConversation
*
bconv
)
{
BonjourData
*
bd
=
NULL
;
PurpleConnection
*
pc
=
NULL
;
if
(
bconv
==
NULL
)
{
return
;
}
pc
=
purple_account_get_connection
(
bconv
->
account
);
PURPLE_ASSERT_CONNECTION_IS_VALID
(
pc
);
bd
=
purple_connection_get_protocol_data
(
pc
);
if
(
bd
)
{
bd
->
xmpp_data
->
pending_conversations
=
g_slist_remove
(
bd
->
xmpp_data
->
pending_conversations
,
bconv
);
}
/* Cancel any file transfers that are waiting to begin */
/* There won't be any transfers if it hasn't been attached to a buddy */
if
(
bconv
->
pb
!=
NULL
&&
bd
!=
NULL
)
{
GSList
*
xfers
,
*
tmp_next
;
xfers
=
bd
->
xfer_lists
;
while
(
xfers
!=
NULL
)
{
PurpleXfer
*
xfer
=
xfers
->
data
;
tmp_next
=
xfers
->
next
;
/* We only need to cancel this if it hasn't actually started transferring. */
/* This will change if we ever support IBB transfers. */
if
(
purple_strequal
(
purple_xfer_get_remote_user
(
xfer
),
purple_buddy_get_name
(
bconv
->
pb
))
&&
(
purple_xfer_get_status
(
xfer
)
==
PURPLE_XFER_STATUS_NOT_STARTED
||
purple_xfer_get_status
(
xfer
)
==
PURPLE_XFER_STATUS_UNKNOWN
))
{
purple_xfer_cancel_remote
(
xfer
);
}
xfers
=
tmp_next
;
}
}
/* Close the socket and remove the watcher */
if
(
bconv
->
socket
!=
NULL
)
{
/* Send the end of the stream to the other end of the conversation */
if
(
bconv
->
sent_stream_start
==
FULLY_SENT
)
{
size_t
len
=
strlen
(
STREAM_END
);
if
(
g_pollable_output_stream_write_nonblocking
(
G_POLLABLE_OUTPUT_STREAM
(
bconv
->
output
),
STREAM_END
,
len
,
bconv
->
cancellable
,
NULL
)
!=
(
gssize
)
len
)
{
purple_debug_error
(
"bonjour"
,
"bonjour_xmpp_close_conversation: "
"couldn't send data
\n
"
);
}
}
/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
purple_gio_graceful_close
(
G_IO_STREAM
(
bconv
->
socket
),
G_INPUT_STREAM
(
bconv
->
input
),
G_OUTPUT_STREAM
(
bconv
->
output
));
}
if
(
bconv
->
rx_handler
!=
0
)
{
g_source_remove
(
bconv
->
rx_handler
);
bconv
->
rx_handler
=
0
;
}
if
(
bconv
->
tx_handler
!=
0
)
{
g_source_remove
(
bconv
->
tx_handler
);
bconv
->
tx_handler
=
0
;
}
/* Cancel any pending operations. */
if
(
bconv
->
cancellable
!=
NULL
)
{
g_cancellable_cancel
(
bconv
->
cancellable
);
g_clear_object
(
&
bconv
->
cancellable
);
}
/* Free all the data related to the conversation */
g_clear_object
(
&
bconv
->
socket
);
g_clear_object
(
&
bconv
->
input
);
g_clear_object
(
&
bconv
->
output
);
g_object_unref
(
G_OBJECT
(
bconv
->
tx_buf
));
if
(
bconv
->
stream_data
!=
NULL
)
{
struct
_stream_start_data
*
ss
=
bconv
->
stream_data
;
g_free
(
ss
->
msg
);
g_free
(
ss
);
}
if
(
bconv
->
context
!=
NULL
)
{
bonjour_parser_setup
(
bconv
);
}
if
(
bconv
->
close_timeout
!=
0
)
{
g_source_remove
(
bconv
->
close_timeout
);
}
g_free
(
bconv
->
buddy_name
);
g_free
(
bconv
->
ip
);
g_free
(
bconv
);
}
void
bonjour_xmpp_stop
(
BonjourXMPP
*
jdata
)
{
/* Close the server socket and remove the watcher */
if
(
jdata
->
service
)
{
g_socket_service_stop
(
jdata
->
service
);
g_socket_listener_close
(
G_SOCKET_LISTENER
(
jdata
->
service
));
g_clear_object
(
&
jdata
->
service
);
}
/* Close all the conversation sockets and remove all the watchers after sending end streams */
if
(
!
purple_account_is_disconnected
(
jdata
->
account
))
{
GSList
*
buddies
,
*
l
;
buddies
=
purple_blist_find_buddies
(
jdata
->
account
,
NULL
);
for
(
l
=
buddies
;
l
;
l
=
l
->
next
)
{
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
((
PurpleBuddy
*
)
l
->
data
);
if
(
bb
&&
bb
->
conversation
)
{
/* Any ongoing connection attempt is cancelled
* when a connection is destroyed */
bonjour_xmpp_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
}
}
g_slist_free
(
buddies
);
}
g_slist_free_full
(
jdata
->
pending_conversations
,
(
GDestroyNotify
)
bonjour_xmpp_close_conversation
);
}
XepIq
*
xep_iq_new
(
void
*
data
,
XepIqType
type
,
const
char
*
to
,
const
char
*
from
,
const
char
*
id
)
{
PurpleXmlNode
*
iq_node
=
NULL
;
XepIq
*
iq
=
NULL
;
g_return_val_if_fail
(
data
!=
NULL
,
NULL
);
g_return_val_if_fail
(
to
!=
NULL
,
NULL
);
g_return_val_if_fail
(
id
!=
NULL
,
NULL
);
iq_node
=
purple_xmlnode_new
(
"iq"
);
purple_xmlnode_set_attrib
(
iq_node
,
"to"
,
to
);
purple_xmlnode_set_attrib
(
iq_node
,
"from"
,
from
);
purple_xmlnode_set_attrib
(
iq_node
,
"id"
,
id
);
switch
(
type
)
{
case
XEP_IQ_SET
:
purple_xmlnode_set_attrib
(
iq_node
,
"type"
,
"set"
);
break
;
case
XEP_IQ_GET
:
purple_xmlnode_set_attrib
(
iq_node
,
"type"
,
"get"
);
break
;
case
XEP_IQ_RESULT
:
purple_xmlnode_set_attrib
(
iq_node
,
"type"
,
"result"
);
break
;
case
XEP_IQ_ERROR
:
purple_xmlnode_set_attrib
(
iq_node
,
"type"
,
"error"
);
break
;
case
XEP_IQ_NONE
:
default
:
purple_xmlnode_set_attrib
(
iq_node
,
"type"
,
"none"
);
break
;
}
iq
=
g_new0
(
XepIq
,
1
);
iq
->
node
=
iq_node
;
iq
->
type
=
type
;
iq
->
data
=
((
BonjourData
*
)
data
)
->
xmpp_data
;
iq
->
to
=
(
char
*
)
to
;
return
iq
;
}
static
gboolean
check_if_blocked
(
PurpleBuddy
*
pb
)
{
gboolean
blocked
=
FALSE
;
GSList
*
l
=
NULL
;
PurpleAccount
*
acc
=
purple_buddy_get_account
(
pb
);
const
gchar
*
name
;
if
(
acc
==
NULL
)
return
FALSE
;
l
=
purple_account_privacy_get_denied
(
acc
);
name
=
purple_buddy_get_name
(
pb
);
if
(
g_slist_find_custom
(
l
,
name
,
(
GCompareFunc
)
purple_utf8_strcasecmp
)
!=
NULL
)
{
const
gchar
*
username
=
bonjour_get_jid
(
acc
);
purple_debug_info
(
"bonjour"
,
"%s has been blocked by %s.
\n
"
,
name
,
username
);
blocked
=
TRUE
;
}
return
blocked
;
}
static
void
xep_iq_parse
(
PurpleXmlNode
*
packet
,
PurpleBuddy
*
pb
)
{
PurpleAccount
*
account
;
PurpleConnection
*
gc
;
if
(
check_if_blocked
(
pb
))
return
;
account
=
purple_buddy_get_account
(
pb
);
gc
=
purple_account_get_connection
(
account
);
if
(
purple_xmlnode_get_child
(
packet
,
"si"
)
!=
NULL
||
purple_xmlnode_get_child
(
packet
,
"error"
)
!=
NULL
)
xep_si_parse
(
gc
,
packet
,
pb
);
else
xep_bytestreams_parse
(
gc
,
packet
,
pb
);
}
int
xep_iq_send_and_free
(
XepIq
*
iq
)
{
int
ret
=
-1
;
PurpleBuddy
*
pb
=
NULL
;
/* start the talk, reuse the message socket */
pb
=
_find_or_start_conversation
((
BonjourXMPP
*
)
iq
->
data
,
iq
->
to
);
/* Send the message */
if
(
pb
!=
NULL
)
{
/* Convert xml node into stream */
gchar
*
msg
=
purple_xmlnode_to_str
(
iq
->
node
,
NULL
);
ret
=
_send_data
(
pb
,
msg
);
g_free
(
msg
);
}
purple_xmlnode_free
(
iq
->
node
);
iq
->
node
=
NULL
;
g_free
(
iq
);
return
(
ret
>=
0
)
?
0
:
-1
;
}
void
append_iface_if_linklocal
(
char
*
ip
,
guint32
interface_param
)
{
GInetAddress
*
addr
;
int
len_remain
=
INET6_ADDRSTRLEN
-
strlen
(
ip
);
if
(
len_remain
<=
1
)
return
;
addr
=
g_inet_address_new_from_string
(
ip
);
if
(
addr
==
NULL
||
g_inet_address_get_family
(
addr
)
!=
G_SOCKET_FAMILY_IPV6
||
!
g_inet_address_get_is_link_local
(
addr
))
{
g_clear_object
(
&
addr
);
return
;
}
g_clear_object
(
&
addr
);
g_snprintf
(
ip
+
strlen
(
ip
),
len_remain
,
"%%%d"
,
interface_param
);
}