pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Update the date in COPYRIGHT as it was a few years behind
release-2.x.y
3 months ago, Gary Kramlich
21a56db5f998
Update the date in COPYRIGHT as it was a few years behind
Testing Done:
None
Reviewed at https://reviews.imfreedom.org/r/3007/
/*
* 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
"internal.h"
#ifndef _WIN32
#include
<net/if.h>
#include
<sys/ioctl.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>
#else
#include
"libc_interface.h"
#endif
#include
<sys/types.h>
/* Solaris */
#if defined (__SVR4) && defined (__sun)
#include
<sys/sockio.h>
#endif
#include
<glib.h>
#ifdef HAVE_UNISTD_H
#include
<unistd.h>
#endif
#include
<fcntl.h>
#ifdef HAVE_GETIFADDRS
#include
<ifaddrs.h>
#endif
#include
"network.h"
#include
"eventloop.h"
#include
"connection.h"
#include
"blist.h"
#include
"xmlnode.h"
#include
"debug.h"
#include
"notify.h"
#include
"util.h"
#include
"jabber.h"
#include
"parser.h"
#include
"bonjour.h"
#include
"buddy.h"
#include
"bonjour_ft.h"
#ifdef _SIZEOF_ADDR_IFREQ
# define HX_SIZE_OF_IFREQ(a) _SIZEOF_ADDR_IFREQ(a)
#else
# define HX_SIZE_OF_IFREQ(a) sizeof(a)
#endif
#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
(
xmlnode
*
packet
,
PurpleBuddy
*
pb
);
static
BonjourJabberConversation
*
bonjour_jabber_conv_new
(
PurpleBuddy
*
pb
,
PurpleAccount
*
account
,
const
char
*
ip
)
{
BonjourJabberConversation
*
bconv
=
g_new0
(
BonjourJabberConversation
,
1
);
bconv
->
socket
=
-1
;
bconv
->
tx_buf
=
purple_circ_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
(
xmlnode
*
node
)
{
gchar
*
contents
;
contents
=
xmlnode_to_str
(
node
,
NULL
);
/* we just want the stuff inside <font></font>
* There isn't stuff exposed in xmlnode.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
_jabber_parse_and_write_message_to_ui
(
xmlnode
*
message_node
,
PurpleBuddy
*
pb
)
{
xmlnode
*
body_node
,
*
html_node
,
*
events_node
;
PurpleConnection
*
gc
=
purple_account_get_connection
(
purple_buddy_get_account
(
pb
));
gchar
*
body
=
NULL
;
body_node
=
xmlnode_get_child
(
message_node
,
"body"
);
html_node
=
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
=
xmlnode_get_child_with_namespace
(
message_node
,
"x"
,
"jabber:x:event"
);
if
(
events_node
!=
NULL
)
{
#if 0
if (xmlnode_get_child(events_node, "composing") != NULL)
composing_event = TRUE;
#endif
if
(
xmlnode_get_child
(
events_node
,
"id"
)
!=
NULL
)
{
/* The user is just typing */
/* TODO: Deal with typing notification */
return
;
}
}
if
(
html_node
!=
NULL
)
{
xmlnode
*
html_body_node
;
html_body_node
=
xmlnode_get_child
(
html_node
,
"body"
);
if
(
html_body_node
!=
NULL
)
{
xmlnode
*
html_body_font_node
;
html_body_font_node
=
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
,
*
ichat_balloon_color
,
*
ichat_text_color
;
font_face
=
xmlnode_get_attrib
(
html_body_font_node
,
"face"
);
/* The absolute iChat font sizes should be converted to 1..7 range */
font_size
=
xmlnode_get_attrib
(
html_body_font_node
,
"ABSZ"
);
if
(
font_size
!=
NULL
)
font_size
=
_font_size_ichat_to_purple
(
atoi
(
font_size
));
/*font_color = xmlnode_get_attrib(html_body_font_node, "color");*/
ichat_balloon_color
=
xmlnode_get_attrib
(
html_body_node
,
"ichatballooncolor"
);
ichat_text_color
=
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
=
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
(
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
=
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 */
serv_got_im
(
gc
,
purple_buddy_get_name
(
pb
),
body
,
0
,
time
(
NULL
));
g_free
(
body
);
}
struct
_match_buddies_by_address_t
{
const
char
*
address
;
GSList
*
matched_buddies
;
};
static
void
_match_buddies_by_address
(
gpointer
value
,
gpointer
data
)
{
PurpleBuddy
*
pb
=
value
;
BonjourBuddy
*
bb
=
NULL
;
struct
_match_buddies_by_address_t
*
mbba
=
data
;
bb
=
purple_buddy_get_protocol_data
(
pb
);
/*
* If the current PurpleBuddy's data is not null, then continue to determine
* whether one of the buddies IPs matches the target IP.
*/
if
(
bb
!=
NULL
)
{
const
char
*
ip
;
GSList
*
tmp
=
bb
->
ips
;
while
(
tmp
)
{
ip
=
tmp
->
data
;
if
(
ip
!=
NULL
&&
g_ascii_strcasecmp
(
ip
,
mbba
->
address
)
==
0
)
{
mbba
->
matched_buddies
=
g_slist_prepend
(
mbba
->
matched_buddies
,
pb
);
break
;
}
tmp
=
tmp
->
next
;
}
}
}
static
void
_send_data_write_cb
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
PurpleBuddy
*
pb
=
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
BonjourJabberConversation
*
bconv
=
bb
->
conversation
;
int
ret
,
writelen
;
writelen
=
purple_circ_buffer_get_max_read
(
bconv
->
tx_buf
);
if
(
writelen
==
0
)
{
purple_input_remove
(
bconv
->
tx_handler
);
bconv
->
tx_handler
=
0
;
return
;
}
ret
=
send
(
bconv
->
socket
,
bconv
->
tx_buf
->
outptr
,
writelen
,
0
);
if
(
ret
<
0
&&
errno
==
EAGAIN
)
return
;
else
if
(
ret
<=
0
)
{
PurpleConversation
*
conv
=
NULL
;
PurpleAccount
*
account
=
NULL
;
const
char
*
error
=
g_strerror
(
errno
);
purple_debug_error
(
"bonjour"
,
"Error sending message to buddy %s error: %s
\n
"
,
purple_buddy_get_name
(
pb
),
error
?
error
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bb
->
name
,
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send message."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
bonjour_jabber_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
return
;
}
purple_circ_buffer_mark_read
(
bconv
->
tx_buf
,
ret
);
}
static
gint
_send_data
(
PurpleBuddy
*
pb
,
char
*
message
)
{
gint
ret
;
int
len
=
strlen
(
message
);
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
BonjourJabberConversation
*
bconv
=
bb
->
conversation
;
/* If we're not ready to actually send, append it to the buffer */
if
(
bconv
->
tx_handler
!=
0
||
bconv
->
connect_data
!=
NULL
||
bconv
->
sent_stream_start
!=
FULLY_SENT
||
!
bconv
->
recv_stream_start
||
purple_circ_buffer_get_max_read
(
bconv
->
tx_buf
)
>
0
)
{
ret
=
-1
;
errno
=
EAGAIN
;
}
else
{
ret
=
send
(
bconv
->
socket
,
message
,
len
,
0
);
}
if
(
ret
==
-1
&&
errno
==
EAGAIN
)
ret
=
0
;
else
if
(
ret
<=
0
)
{
PurpleConversation
*
conv
;
PurpleAccount
*
account
;
const
char
*
error
=
g_strerror
(
errno
);
purple_debug_error
(
"bonjour"
,
"Error sending message to buddy %s error: %s
\n
"
,
purple_buddy_get_name
(
pb
),
error
?
error
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bb
->
name
,
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send message."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
bonjour_jabber_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
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
)
bconv
->
tx_handler
=
purple_input_add
(
bconv
->
socket
,
PURPLE_INPUT_WRITE
,
_send_data_write_cb
,
pb
);
purple_circ_buffer_append
(
bconv
->
tx_buf
,
message
+
ret
,
len
-
ret
);
}
return
ret
;
}
void
bonjour_jabber_process_packet
(
PurpleBuddy
*
pb
,
xmlnode
*
packet
)
{
g_return_if_fail
(
packet
!=
NULL
);
g_return_if_fail
(
pb
!=
NULL
);
if
(
purple_strequal
(
packet
->
name
,
"message"
))
_jabber_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_jabber_stream_ended
(
BonjourJabberConversation
*
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
);
#if 0
if(bconv->pb != NULL) {
PurpleConversation *conv;
conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account);
if (conv != NULL) {
char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name);
purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
g_free(tmp);
}
}
#endif
/* Close the socket, clear the watcher and free memory */
bonjour_jabber_close_conversation
(
bconv
);
if
(
bb
)
bb
->
conversation
=
NULL
;
}
static
void
_client_socket_handler
(
gpointer
data
,
gint
socket
,
PurpleInputCondition
condition
)
{
BonjourJabberConversation
*
bconv
=
data
;
gssize
len
;
static
char
message
[
4096
];
/* Read the data from the socket */
if
((
len
=
recv
(
socket
,
message
,
sizeof
(
message
)
-
1
,
0
))
<
0
)
{
/* There have been an error reading from the socket */
if
(
len
!=
-1
||
errno
!=
EAGAIN
)
{
const
char
*
err
=
g_strerror
(
errno
);
purple_debug_warning
(
"bonjour"
,
"receive of %"
G_GSSIZE_FORMAT
" error: %s
\n
"
,
len
,
err
?
err
:
"(null)"
);
bonjour_jabber_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 */
}
return
;
}
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_jabber_stream_ended
(
bconv
);
return
;
}
message
[
len
]
=
'\0'
;
purple_debug_info
(
"bonjour"
,
"Receive: -%s- %"
G_GSSIZE_FORMAT
" bytes
\n
"
,
message
,
len
);
bonjour_parser_process
(
bconv
,
message
,
len
);
}
struct
_stream_start_data
{
char
*
msg
;
};
static
void
_start_stream
(
gpointer
data
,
gint
source
,
PurpleInputCondition
condition
)
{
BonjourJabberConversation
*
bconv
=
data
;
struct
_stream_start_data
*
ss
=
bconv
->
stream_data
;
int
len
,
ret
;
len
=
strlen
(
ss
->
msg
);
/* Start Stream */
ret
=
send
(
source
,
ss
->
msg
,
len
,
0
);
if
(
ret
==
-1
&&
errno
==
EAGAIN
)
return
;
else
if
(
ret
<=
0
)
{
const
char
*
err
=
g_strerror
(
errno
);
PurpleConversation
*
conv
;
const
char
*
bname
=
bconv
->
buddy_name
;
BonjourBuddy
*
bb
=
NULL
;
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
\n
"
,
bname
?
bname
:
"(unknown)"
,
bconv
->
ip
,
err
?
err
:
"(null)"
);
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bname
,
bconv
->
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
bonjour_jabber_close_conversation
(
bconv
);
if
(
bb
!=
NULL
)
bb
->
conversation
=
NULL
;
return
;
}
/* This is EXTREMELY unlikely to happen */
if
(
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 */
purple_input_remove
(
bconv
->
tx_handler
);
bconv
->
tx_handler
=
0
;
bconv
->
sent_stream_start
=
FULLY_SENT
;
bonjour_jabber_stream_started
(
bconv
);
}
static
gboolean
bonjour_jabber_send_stream_init
(
BonjourJabberConversation
*
bconv
,
int
client_socket
)
{
int
ret
,
len
;
char
*
stream_start
;
const
char
*
bname
=
bconv
->
buddy_name
;
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
=
send
(
client_socket
,
stream_start
,
len
,
0
);
if
(
ret
==
-1
&&
errno
==
EAGAIN
)
ret
=
0
;
else
if
(
ret
<=
0
)
{
const
char
*
err
=
g_strerror
(
errno
);
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at %s error: %s
\n
"
,
(
*
bname
)
?
bname
:
"(unknown)"
,
bconv
->
ip
,
err
?
err
:
"(null)"
);
if
(
bconv
->
pb
)
{
PurpleConversation
*
conv
;
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bname
,
bconv
->
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
}
close
(
client_socket
);
g_free
(
stream_start
);
return
FALSE
;
}
/* This is unlikely to happen */
if
(
ret
<
len
)
{
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 */
bconv
->
tx_handler
=
purple_input_add
(
client_socket
,
PURPLE_INPUT_WRITE
,
_start_stream
,
bconv
);
}
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_jabber_stream_started
(
BonjourJabberConversation
*
bconv
)
{
if
(
bconv
->
sent_stream_start
==
NOT_SENT
&&
!
bonjour_jabber_send_stream_init
(
bconv
,
bconv
->
socket
))
{
const
char
*
err
=
g_strerror
(
errno
);
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
\n
"
,
bname
?
bname
:
"(unknown)"
,
bconv
->
ip
,
err
?
err
:
"(null)"
);
if
(
bconv
->
pb
)
{
PurpleConversation
*
conv
;
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bname
,
bconv
->
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
}
/* We don't want to recieve anything else */
close
(
bconv
->
socket
);
bconv
->
socket
=
-1
;
/* This must be asynchronous because it destroys the parser and we
* may be in the middle of parsing.
*/
async_bonjour_jabber_close_conversation
(
bconv
);
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_circ_buffer_get_max_read
(
bconv
->
tx_buf
)
>
0
)
{
/* Watch for when we can write the buffered messages */
bconv
->
tx_handler
=
purple_input_add
(
bconv
->
socket
,
PURPLE_INPUT_WRITE
,
_send_data_write_cb
,
bconv
->
pb
);
/* We can probably write the data right now. */
_send_data_write_cb
(
bconv
->
pb
,
bconv
->
socket
,
PURPLE_INPUT_WRITE
);
}
}
#ifndef INET6_ADDRSTRLEN
#define INET6_ADDRSTRLEN 46
#endif
static
void
_server_socket_handler
(
gpointer
data
,
int
server_socket
,
PurpleInputCondition
condition
)
{
BonjourJabber
*
jdata
=
data
;
common_sockaddr_t
their_addr
;
/* connector's address information */
socklen_t
sin_size
=
sizeof
(
common_sockaddr_t
);
int
client_socket
;
#ifdef HAVE_INET_NTOP
char
addrstr
[
INET6_ADDRSTRLEN
];
#endif
const
char
*
address_text
;
struct
_match_buddies_by_address_t
*
mbba
;
BonjourJabberConversation
*
bconv
;
GSList
*
buddies
;
/* Check that it is a read condition */
if
(
condition
!=
PURPLE_INPUT_READ
)
return
;
memset
(
&
their_addr
,
0
,
sin_size
);
if
((
client_socket
=
accept
(
server_socket
,
&
their_addr
.
sa
,
&
sin_size
))
==
-1
)
return
;
_purple_network_set_common_socket_flags
(
client_socket
);
/* Look for the buddy that has opened the conversation and fill information */
#ifdef HAVE_INET_NTOP
if
(
their_addr
.
sa
.
sa_family
==
AF_INET6
)
{
address_text
=
inet_ntop
(
their_addr
.
sa
.
sa_family
,
&
their_addr
.
in6
.
sin6_addr
,
addrstr
,
sizeof
(
addrstr
));
append_iface_if_linklocal
(
addrstr
,
their_addr
.
in6
.
sin6_scope_id
);
}
else
{
address_text
=
inet_ntop
(
their_addr
.
sa
.
sa_family
,
&
their_addr
.
in
.
sin_addr
,
addrstr
,
sizeof
(
addrstr
));
}
#else
address_text
=
inet_ntoa
(
their_addr
.
in
.
sin_addr
);
#endif
purple_debug_info
(
"bonjour"
,
"Received incoming connection from %s.
\n
"
,
address_text
);
mbba
=
g_new0
(
struct
_match_buddies_by_address_t
,
1
);
mbba
->
address
=
address_text
;
buddies
=
purple_find_buddies
(
jdata
->
account
,
NULL
);
g_slist_foreach
(
buddies
,
_match_buddies_by_address
,
mbba
);
g_slist_free
(
buddies
);
if
(
mbba
->
matched_buddies
==
NULL
)
{
purple_debug_info
(
"bonjour"
,
"We don't like invisible buddies, this is not a superheroes comic
\n
"
);
g_free
(
mbba
);
close
(
client_socket
);
return
;
}
g_slist_free
(
mbba
->
matched_buddies
);
g_free
(
mbba
);
/* 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_jabber_conv_new
(
NULL
,
jdata
->
account
,
address_text
);
/* We wait for the stream start before doing anything else */
bconv
->
socket
=
client_socket
;
bconv
->
rx_handler
=
purple_input_add
(
client_socket
,
PURPLE_INPUT_READ
,
_client_socket_handler
,
bconv
);
}
static
int
start_serversocket_listening
(
int
port
,
int
socket
,
struct
sockaddr
*
addr
,
size_t
addr_size
,
gboolean
ip6
,
gboolean
allow_port_fallback
)
{
int
ret_port
=
port
;
purple_debug_info
(
"bonjour"
,
"Attempting to bind IPv%d socket to port %d.
\n
"
,
ip6
?
6
:
4
,
port
);
/* Try to use the specified port - if it isn't available, use a random port */
if
(
bind
(
socket
,
addr
,
addr_size
)
!=
0
)
{
purple_debug_info
(
"bonjour"
,
"Unable to bind to specified "
"port %i: %s
\n
"
,
port
,
g_strerror
(
errno
));
if
(
!
allow_port_fallback
)
{
purple_debug_warning
(
"bonjour"
,
"Not attempting random port assignment.
\n
"
);
return
-1
;
}
#ifdef PF_INET6
if
(
ip6
)
((
struct
sockaddr_in6
*
)
addr
)
->
sin6_port
=
0
;
else
#endif
((
struct
sockaddr_in
*
)
addr
)
->
sin_port
=
0
;
if
(
bind
(
socket
,
addr
,
addr_size
)
!=
0
)
{
purple_debug_error
(
"bonjour"
,
"Unable to bind IPv%d socket to port: %s
\n
"
,
ip6
?
6
:
4
,
g_strerror
(
errno
));
return
-1
;
}
ret_port
=
purple_network_get_port_from_fd
(
socket
);
}
purple_debug_info
(
"bonjour"
,
"Bound IPv%d socket to port %d.
\n
"
,
ip6
?
6
:
4
,
ret_port
);
/* Attempt to listen on the bound socket */
if
(
listen
(
socket
,
10
)
!=
0
)
{
purple_debug_error
(
"bonjour"
,
"Unable to listen on IPv%d socket: %s
\n
"
,
ip6
?
6
:
4
,
g_strerror
(
errno
));
return
-1
;
}
#if 0
/* TODO: Why isn't this being used? */
data->socket = purple_network_listen(jdata->port, SOCK_STREAM);
if (jdata->socket == -1)
{
purple_debug_error("bonjour", "No se ha podido crear el socket\n");
}
#endif
return
ret_port
;
}
gint
bonjour_jabber_start
(
BonjourJabber
*
jdata
)
{
int
ipv6_port
=
-1
,
ipv4_port
=
-1
;
/* Open a listening socket for incoming conversations */
#ifdef PF_INET6
jdata
->
socket6
=
socket
(
PF_INET6
,
SOCK_STREAM
,
0
);
#endif
jdata
->
socket
=
socket
(
PF_INET
,
SOCK_STREAM
,
0
);
if
(
jdata
->
socket
==
-1
&&
jdata
->
socket6
==
-1
)
{
purple_debug_error
(
"bonjour"
,
"Unable to create socket: %s"
,
g_strerror
(
errno
));
return
-1
;
}
#ifdef PF_INET6
if
(
jdata
->
socket6
!=
-1
)
{
struct
sockaddr_in6
addr6
;
#ifdef IPV6_V6ONLY
int
on
=
1
;
if
(
setsockopt
(
jdata
->
socket6
,
IPPROTO_IPV6
,
IPV6_V6ONLY
,
&
on
,
sizeof
(
on
))
!=
0
)
{
purple_debug_error
(
"bonjour"
,
"couldn't force IPv6
\n
"
);
return
-1
;
}
#endif
memset
(
&
addr6
,
0
,
sizeof
(
addr6
));
addr6
.
sin6_family
=
AF_INET6
;
addr6
.
sin6_port
=
htons
(
jdata
->
port
);
addr6
.
sin6_addr
=
in6addr_any
;
ipv6_port
=
start_serversocket_listening
(
jdata
->
port
,
jdata
->
socket6
,
(
struct
sockaddr
*
)
&
addr6
,
sizeof
(
addr6
),
TRUE
,
TRUE
);
/* Open a watcher in the socket we have just opened */
if
(
ipv6_port
>
0
)
{
jdata
->
watcher_id6
=
purple_input_add
(
jdata
->
socket6
,
PURPLE_INPUT_READ
,
_server_socket_handler
,
jdata
);
jdata
->
port
=
ipv6_port
;
}
else
{
purple_debug_error
(
"bonjour"
,
"Failed to start listening on IPv6 socket.
\n
"
);
close
(
jdata
->
socket6
);
jdata
->
socket6
=
-1
;
}
}
#endif
if
(
jdata
->
socket
!=
-1
)
{
struct
sockaddr_in
addr4
;
memset
(
&
addr4
,
0
,
sizeof
(
addr4
));
addr4
.
sin_family
=
AF_INET
;
addr4
.
sin_port
=
htons
(
jdata
->
port
);
ipv4_port
=
start_serversocket_listening
(
jdata
->
port
,
jdata
->
socket
,
(
struct
sockaddr
*
)
&
addr4
,
sizeof
(
addr4
),
FALSE
,
TRUE
);
/* Open a watcher in the socket we have just opened */
if
(
ipv4_port
>
0
)
{
jdata
->
watcher_id
=
purple_input_add
(
jdata
->
socket
,
PURPLE_INPUT_READ
,
_server_socket_handler
,
jdata
);
jdata
->
port
=
ipv4_port
;
}
else
{
purple_debug_error
(
"bonjour"
,
"Failed to start listening on IPv4 socket.
\n
"
);
close
(
jdata
->
socket
);
jdata
->
socket
=
-1
;
}
}
if
(
!
(
ipv6_port
>
0
||
ipv4_port
>
0
))
{
purple_debug_error
(
"bonjour"
,
"Unable to listen on socket: %s"
,
g_strerror
(
errno
));
return
-1
;
}
return
jdata
->
port
;
}
static
void
_connected_to_buddy
(
gpointer
data
,
gint
source
,
const
gchar
*
error
)
{
PurpleBuddy
*
pb
=
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
bb
->
conversation
->
connect_data
=
NULL
;
if
(
source
<
0
)
{
PurpleConversation
*
conv
=
NULL
;
PurpleAccount
*
account
=
NULL
;
GSList
*
tmp
=
bb
->
ips
;
purple_debug_error
(
"bonjour"
,
"Error connecting to buddy %s at %s:%d (%s); Trying next IP address
\n
"
,
purple_buddy_get_name
(
pb
),
bb
->
conversation
->
ip
,
bb
->
port_p2pj
,
error
);
/* There may be multiple entries for the same IP - one per
* presence recieved (e.g. multiple interfaces).
* We need to make sure that we find the previously used entry.
*/
while
(
tmp
&&
bb
->
conversation
->
ip_link
!=
tmp
->
data
)
tmp
=
g_slist_next
(
tmp
);
if
(
tmp
)
tmp
=
g_slist_next
(
tmp
);
account
=
purple_buddy_get_account
(
pb
);
if
(
tmp
!=
NULL
)
{
const
gchar
*
ip
;
PurpleProxyConnectData
*
connect_data
;
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
);
connect_data
=
purple_proxy_connect
(
purple_account_get_connection
(
account
),
account
,
ip
,
bb
->
port_p2pj
,
_connected_to_buddy
,
pb
);
if
(
connect_data
!=
NULL
)
{
g_free
(
bb
->
conversation
->
ip
);
bb
->
conversation
->
ip
=
g_strdup
(
ip
);
bb
->
conversation
->
connect_data
=
connect_data
;
return
;
}
}
purple_debug_error
(
"bonjour"
,
"No more addresses for buddy %s. Aborting"
,
purple_buddy_get_name
(
pb
));
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bb
->
name
,
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
bonjour_jabber_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
return
;
}
if
(
!
bonjour_jabber_send_stream_init
(
bb
->
conversation
,
source
))
{
const
char
*
err
=
g_strerror
(
errno
);
PurpleConversation
*
conv
=
NULL
;
PurpleAccount
*
account
=
NULL
;
purple_debug_error
(
"bonjour"
,
"Error starting stream with buddy %s at %s:%d error: %s
\n
"
,
purple_buddy_get_name
(
pb
),
bb
->
conversation
->
ip
,
bb
->
port_p2pj
,
err
?
err
:
"(null)"
);
account
=
purple_buddy_get_account
(
pb
);
conv
=
purple_find_conversation_with_account
(
PURPLE_CONV_TYPE_IM
,
bb
->
name
,
account
);
if
(
conv
!=
NULL
)
purple_conversation_write
(
conv
,
NULL
,
_
(
"Unable to send the message, the conversation couldn't be started."
),
PURPLE_MESSAGE_SYSTEM
,
time
(
NULL
));
close
(
source
);
bonjour_jabber_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
return
;
}
/* Start listening for the stream acknowledgement */
bb
->
conversation
->
socket
=
source
;
bb
->
conversation
->
rx_handler
=
purple_input_add
(
source
,
PURPLE_INPUT_READ
,
_client_socket_handler
,
bb
->
conversation
);
}
void
bonjour_jabber_conv_match_by_name
(
BonjourJabberConversation
*
bconv
)
{
PurpleBuddy
*
pb
=
NULL
;
BonjourBuddy
*
bb
=
NULL
;
g_return_if_fail
(
bconv
->
ip
!=
NULL
);
g_return_if_fail
(
bconv
->
pb
==
NULL
);
pb
=
purple_find_buddy
(
bconv
->
account
,
bconv
->
buddy_name
);
if
(
pb
&&
(
bb
=
purple_buddy_get_protocol_data
(
pb
)))
{
const
char
*
ip
;
GSList
*
tmp
=
bb
->
ips
;
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 */
while
(
tmp
)
{
ip
=
tmp
->
data
;
if
(
ip
!=
NULL
&&
g_ascii_strcasecmp
(
ip
,
bconv
->
ip
)
==
0
)
{
BonjourJabber
*
jdata
=
((
BonjourData
*
)
bconv
->
account
->
gc
->
proto_data
)
->
jabber_data
;
purple_debug_info
(
"bonjour"
,
"Matched buddy %s to incoming conversation
\"
from
\"
attrib and IP (%s)
\n
"
,
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_jabber_close_conversation
(
bb
->
conversation
);
bconv
->
pb
=
pb
;
bb
->
conversation
=
bconv
;
break
;
}
tmp
=
tmp
->
next
;
}
}
/* 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_jabber_close_conversation
(
bconv
);
}
}
void
bonjour_jabber_conv_match_by_ip
(
BonjourJabberConversation
*
bconv
)
{
BonjourJabber
*
jdata
=
((
BonjourData
*
)
bconv
->
account
->
gc
->
proto_data
)
->
jabber_data
;
struct
_match_buddies_by_address_t
*
mbba
;
GSList
*
buddies
;
mbba
=
g_new0
(
struct
_match_buddies_by_address_t
,
1
);
mbba
->
address
=
bconv
->
ip
;
buddies
=
purple_find_buddies
(
jdata
->
account
,
NULL
);
g_slist_foreach
(
buddies
,
_match_buddies_by_address
,
mbba
);
g_slist_free
(
buddies
);
/* If there is exactly one match, use it */
if
(
mbba
->
matched_buddies
!=
NULL
)
{
if
(
mbba
->
matched_buddies
->
next
!=
NULL
)
purple_debug_error
(
"bonjour"
,
"More than one buddy matched for ip %s.
\n
"
,
bconv
->
ip
);
else
{
PurpleBuddy
*
pb
=
mbba
->
matched_buddies
->
data
;
BonjourBuddy
*
bb
=
purple_buddy_get_protocol_data
(
pb
);
purple_debug_info
(
"bonjour"
,
"Matched buddy %s to incoming conversation using IP (%s)
\n
"
,
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_jabber_close_conversation
(
bb
->
conversation
);
bconv
->
pb
=
pb
;
bb
->
conversation
=
bconv
;
}
}
else
purple_debug_error
(
"bonjour"
,
"No buddies matched for ip %s.
\n
"
,
bconv
->
ip
);
/* 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_jabber_close_conversation
(
bconv
);
}
g_slist_free
(
mbba
->
matched_buddies
);
g_free
(
mbba
);
}
static
PurpleBuddy
*
_find_or_start_conversation
(
BonjourJabber
*
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_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
)
{
PurpleProxyConnectData
*
connect_data
;
PurpleProxyInfo
*
proxy_info
;
const
char
*
ip
=
bb
->
ips
->
data
;
/* Start with the first IP address. */
purple_debug_info
(
"bonjour"
,
"Starting conversation with %s at %s:%d
\n
"
,
to
,
ip
,
bb
->
port_p2pj
);
/* Make sure that the account always has a proxy of "none".
* This is kind of dirty, but proxy_connect_none() isn't exposed. */
proxy_info
=
purple_account_get_proxy_info
(
jdata
->
account
);
if
(
proxy_info
==
NULL
)
{
proxy_info
=
purple_proxy_info_new
();
purple_account_set_proxy_info
(
jdata
->
account
,
proxy_info
);
}
purple_proxy_info_set_type
(
proxy_info
,
PURPLE_PROXY_NONE
);
connect_data
=
purple_proxy_connect
(
purple_account_get_connection
(
jdata
->
account
),
jdata
->
account
,
ip
,
bb
->
port_p2pj
,
_connected_to_buddy
,
pb
);
if
(
connect_data
==
NULL
)
{
purple_debug_error
(
"bonjour"
,
"Unable to connect to buddy (%s).
\n
"
,
to
);
return
NULL
;
}
bb
->
conversation
=
bonjour_jabber_conv_new
(
pb
,
jdata
->
account
,
ip
);
bb
->
conversation
->
connect_data
=
connect_data
;
bb
->
conversation
->
ip_link
=
ip
;
/* We don't want _send_data() to register the tx_handler;
* that neeeds to wait until we're actually connected. */
bb
->
conversation
->
tx_handler
=
0
;
}
return
pb
;
}
int
bonjour_jabber_send_message
(
BonjourJabber
*
jdata
,
const
gchar
*
to
,
const
gchar
*
body
)
{
xmlnode
*
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
=
xmlnode_new
(
"message"
);
xmlnode_set_attrib
(
message_node
,
"to"
,
bb
->
name
);
xmlnode_set_attrib
(
message_node
,
"from"
,
bonjour_get_jid
(
jdata
->
account
));
xmlnode_set_attrib
(
message_node
,
"type"
,
"chat"
);
/* Enclose the message from the UI within a "font" node */
node
=
xmlnode_new_child
(
message_node
,
"body"
);
xmlnode_insert_data
(
node
,
message
,
strlen
(
message
));
g_free
(
message
);
node
=
xmlnode_new_child
(
message_node
,
"html"
);
xmlnode_set_namespace
(
node
,
"http://www.w3.org/1999/xhtml"
);
node
=
xmlnode_new_child
(
node
,
"body"
);
message
=
g_strdup_printf
(
"<font>%s</font>"
,
xhtml
);
node2
=
xmlnode_from_str
(
message
,
strlen
(
message
));
g_free
(
xhtml
);
g_free
(
message
);
xmlnode_insert_child
(
node
,
node2
);
node
=
xmlnode_new_child
(
message_node
,
"x"
);
xmlnode_set_namespace
(
node
,
"jabber:x:event"
);
xmlnode_insert_child
(
node
,
xmlnode_new
(
"composing"
));
message
=
xmlnode_to_str
(
message_node
,
NULL
);
xmlnode_free
(
message_node
);
ret
=
_send_data
(
pb
,
message
)
>=
0
;
g_free
(
message
);
return
ret
;
}
static
gboolean
_async_bonjour_jabber_close_conversation_cb
(
gpointer
data
)
{
BonjourJabberConversation
*
bconv
=
data
;
bonjour_jabber_close_conversation
(
bconv
);
return
FALSE
;
}
void
async_bonjour_jabber_close_conversation
(
BonjourJabberConversation
*
bconv
)
{
BonjourJabber
*
jdata
=
((
BonjourData
*
)
bconv
->
account
->
gc
->
proto_data
)
->
jabber_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
=
purple_timeout_add
(
0
,
_async_bonjour_jabber_close_conversation_cb
,
bconv
);
}
void
bonjour_jabber_close_conversation
(
BonjourJabberConversation
*
bconv
)
{
if
(
bconv
!=
NULL
)
{
BonjourData
*
bd
=
NULL
;
if
(
PURPLE_CONNECTION_IS_VALID
(
bconv
->
account
->
gc
))
{
bd
=
bconv
->
account
->
gc
->
proto_data
;
bd
->
jabber_data
->
pending_conversations
=
g_slist_remove
(
bd
->
jabber_data
->
pending_conversations
,
bconv
);
}
/* Cancel any file transfers that are waiting to begin */
/* There wont 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
(
xfer
->
who
,
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
>=
0
)
{
/* 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
(
send
(
bconv
->
socket
,
STREAM_END
,
len
,
0
)
!=
(
gssize
)
len
)
{
purple_debug_error
(
"bonjour"
,
"bonjour_jabber_close_conversation: "
"couldn't send data
\n
"
);
}
}
/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
close
(
bconv
->
socket
);
}
if
(
bconv
->
rx_handler
!=
0
)
purple_input_remove
(
bconv
->
rx_handler
);
if
(
bconv
->
tx_handler
>
0
)
purple_input_remove
(
bconv
->
tx_handler
);
/* Free all the data related to the conversation */
purple_circ_buffer_destroy
(
bconv
->
tx_buf
);
if
(
bconv
->
connect_data
!=
NULL
)
purple_proxy_connect_cancel
(
bconv
->
connect_data
);
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
)
purple_timeout_remove
(
bconv
->
close_timeout
);
g_free
(
bconv
->
buddy_name
);
g_free
(
bconv
->
ip
);
g_free
(
bconv
);
}
}
void
bonjour_jabber_stop
(
BonjourJabber
*
jdata
)
{
/* Close the server socket and remove the watcher */
if
(
jdata
->
socket
>=
0
)
close
(
jdata
->
socket
);
if
(
jdata
->
watcher_id
>
0
)
purple_input_remove
(
jdata
->
watcher_id
);
if
(
jdata
->
socket6
>=
0
)
close
(
jdata
->
socket6
);
if
(
jdata
->
watcher_id6
>
0
)
purple_input_remove
(
jdata
->
watcher_id6
);
/* Close all the conversation sockets and remove all the watchers after sending end streams */
if
(
jdata
->
account
->
gc
!=
NULL
)
{
GSList
*
buddies
,
*
l
;
buddies
=
purple_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
* by _purple_connection_destroy */
bb
->
conversation
->
connect_data
=
NULL
;
bonjour_jabber_close_conversation
(
bb
->
conversation
);
bb
->
conversation
=
NULL
;
}
}
g_slist_free
(
buddies
);
}
while
(
jdata
->
pending_conversations
!=
NULL
)
{
bonjour_jabber_close_conversation
(
jdata
->
pending_conversations
->
data
);
jdata
->
pending_conversations
=
g_slist_delete_link
(
jdata
->
pending_conversations
,
jdata
->
pending_conversations
);
}
}
XepIq
*
xep_iq_new
(
void
*
data
,
XepIqType
type
,
const
char
*
to
,
const
char
*
from
,
const
char
*
id
)
{
xmlnode
*
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
=
xmlnode_new
(
"iq"
);
xmlnode_set_attrib
(
iq_node
,
"to"
,
to
);
xmlnode_set_attrib
(
iq_node
,
"from"
,
from
);
xmlnode_set_attrib
(
iq_node
,
"id"
,
id
);
switch
(
type
)
{
case
XEP_IQ_SET
:
xmlnode_set_attrib
(
iq_node
,
"type"
,
"set"
);
break
;
case
XEP_IQ_GET
:
xmlnode_set_attrib
(
iq_node
,
"type"
,
"get"
);
break
;
case
XEP_IQ_RESULT
:
xmlnode_set_attrib
(
iq_node
,
"type"
,
"result"
);
break
;
case
XEP_IQ_ERROR
:
xmlnode_set_attrib
(
iq_node
,
"type"
,
"error"
);
break
;
case
XEP_IQ_NONE
:
default
:
xmlnode_set_attrib
(
iq_node
,
"type"
,
"none"
);
break
;
}
iq
=
g_new0
(
XepIq
,
1
);
iq
->
node
=
iq_node
;
iq
->
type
=
type
;
iq
->
data
=
((
BonjourData
*
)
data
)
->
jabber_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
);
if
(
acc
==
NULL
)
return
FALSE
;
acc
=
purple_buddy_get_account
(
pb
);
for
(
l
=
acc
->
deny
;
l
!=
NULL
;
l
=
l
->
next
)
{
const
gchar
*
name
=
purple_buddy_get_name
(
pb
);
const
gchar
*
username
=
bonjour_get_jid
(
acc
);
if
(
!
purple_utf8_strcasecmp
(
name
,
(
char
*
)
l
->
data
))
{
purple_debug_info
(
"bonjour"
,
"%s has been blocked by %s.
\n
"
,
name
,
username
);
blocked
=
TRUE
;
break
;
}
}
return
blocked
;
}
static
void
xep_iq_parse
(
xmlnode
*
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
(
xmlnode_get_child
(
packet
,
"si"
)
!=
NULL
||
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
((
BonjourJabber
*
)
iq
->
data
,
iq
->
to
);
/* Send the message */
if
(
pb
!=
NULL
)
{
/* Convert xml node into stream */
gchar
*
msg
=
xmlnode_to_str
(
iq
->
node
,
NULL
);
ret
=
_send_data
(
pb
,
msg
);
g_free
(
msg
);
}
xmlnode_free
(
iq
->
node
);
iq
->
node
=
NULL
;
g_free
(
iq
);
return
(
ret
>=
0
)
?
0
:
-1
;
}
/* This returns a list containing all non-localhost IPs */
GSList
*
bonjour_jabber_get_local_ips
(
int
fd
)
{
GSList
*
ips
=
NULL
;
const
char
*
address_text
;
int
ret
;
#ifdef HAVE_GETIFADDRS
/* This is required for IPv6 */
{
struct
ifaddrs
*
ifap
,
*
ifa
;
struct
sockaddr
*
addr
;
char
addrstr
[
INET6_ADDRSTRLEN
];
ret
=
getifaddrs
(
&
ifap
);
if
(
ret
!=
0
)
{
const
char
*
error
=
g_strerror
(
errno
);
purple_debug_error
(
"bonjour"
,
"getifaddrs() error: %s
\n
"
,
error
?
error
:
"(null)"
);
return
NULL
;
}
for
(
ifa
=
ifap
;
ifa
!=
NULL
;
ifa
=
ifa
->
ifa_next
)
{
if
(
!
(
ifa
->
ifa_flags
&
IFF_RUNNING
)
||
(
ifa
->
ifa_flags
&
IFF_LOOPBACK
)
||
ifa
->
ifa_addr
==
NULL
)
continue
;
addr
=
ifa
->
ifa_addr
;
address_text
=
NULL
;
switch
(
addr
->
sa_family
)
{
case
AF_INET
:
address_text
=
inet_ntop
(
addr
->
sa_family
,
&
((
struct
sockaddr_in
*
)
addr
)
->
sin_addr
,
addrstr
,
sizeof
(
addrstr
));
break
;
#ifdef PF_INET6
case
AF_INET6
:
address_text
=
inet_ntop
(
addr
->
sa_family
,
&
((
struct
sockaddr_in6
*
)
addr
)
->
sin6_addr
,
addrstr
,
sizeof
(
addrstr
));
break
;
#endif
}
if
(
address_text
!=
NULL
)
{
if
(
addr
->
sa_family
==
AF_INET
)
ips
=
g_slist_append
(
ips
,
g_strdup
(
address_text
));
else
ips
=
g_slist_prepend
(
ips
,
g_strdup
(
address_text
));
}
}
freeifaddrs
(
ifap
);
}
#else
{
char
*
tmp
;
struct
ifconf
ifc
;
struct
ifreq
*
ifr
;
char
buffer
[
1024
];
struct
sockaddr_in
*
sinptr
;
int
source
=
fd
;
if
(
fd
<
0
)
source
=
socket
(
PF_INET
,
SOCK_STREAM
,
0
);
ifc
.
ifc_len
=
sizeof
(
buffer
);
ifc
.
ifc_req
=
(
struct
ifreq
*
)
buffer
;
ret
=
ioctl
(
source
,
SIOCGIFCONF
,
&
ifc
);
if
(
fd
<
0
)
close
(
source
);
if
(
ret
<
0
)
{
const
char
*
error
=
g_strerror
(
errno
);
purple_debug_error
(
"bonjour"
,
"ioctl(SIOCGIFCONF) error: %s
\n
"
,
error
?
error
:
"(null)"
);
return
NULL
;
}
tmp
=
buffer
;
while
(
tmp
<
buffer
+
ifc
.
ifc_len
)
{
ifr
=
(
struct
ifreq
*
)
tmp
;
tmp
+=
HX_SIZE_OF_IFREQ
(
*
ifr
);
if
(
ifr
->
ifr_addr
.
sa_family
==
AF_INET
)
{
sinptr
=
(
struct
sockaddr_in
*
)
&
ifr
->
ifr_addr
;
if
((
ntohl
(
sinptr
->
sin_addr
.
s_addr
)
>>
24
)
!=
127
)
{
address_text
=
inet_ntoa
(
sinptr
->
sin_addr
);
ips
=
g_slist_prepend
(
ips
,
g_strdup
(
address_text
));
}
}
}
}
#endif
return
ips
;
}
void
append_iface_if_linklocal
(
char
*
ip
,
guint32
interface_param
)
{
struct
in6_addr
in6_addr
;
int
len_remain
=
INET6_ADDRSTRLEN
-
strlen
(
ip
);
if
(
len_remain
<=
1
)
return
;
if
(
inet_pton
(
AF_INET6
,
ip
,
&
in6_addr
)
!=
1
||
!
IN6_IS_ADDR_LINKLOCAL
(
&
in6_addr
))
return
;
snprintf
(
ip
+
strlen
(
ip
),
len_remain
,
"%%%d"
,
interface_param
);
}