pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
closing merged branch
port-changes-from-branch-2.x.y-to-default
2020-02-03, Gary Kramlich
2f836435c33c
closing merged branch
/*
* 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 02110-1301, USA
*/
#include
"internal.h"
#include
<purple.h>
#include
"bonjour.h"
#include
"bonjour_ft.h"
static
void
bonjour_bytestreams_init
(
PurpleXfer
*
xfer
);
static
void
bonjour_bytestreams_connect
(
PurpleXfer
*
xfer
);
static
void
bonjour_xfer_init
(
PurpleXfer
*
xfer
);
static
void
bonjour_xfer_receive
(
PurpleConnection
*
pc
,
const
char
*
id
,
const
char
*
sid
,
const
char
*
from
,
goffset
filesize
,
const
char
*
filename
,
int
option
);
/* Look for specific xfer handle */
static
unsigned
int
next_id
=
0
;
struct
_XepXfer
{
PurpleXfer
parent
;
void
*
data
;
char
*
filename
;
int
filesize
;
char
*
iq_id
;
char
*
sid
;
char
*
recv_id
;
char
*
buddy_ip
;
int
mode
;
PurpleNetworkListenData
*
listen_data
;
int
sock5_req_state
;
int
rxlen
;
char
rx_buf
[
0x500
];
char
tx_buf
[
0x500
];
PurpleProxyInfo
*
proxy_info
;
PurpleProxyConnectData
*
proxy_connection
;
char
*
jid
;
char
*
proxy_host
;
int
proxy_port
;
PurpleXmlNode
*
streamhost
;
PurpleBuddy
*
pb
;
};
G_DEFINE_DYNAMIC_TYPE
(
XepXfer
,
xep_xfer
,
PURPLE_TYPE_XFER
);
static
void
xep_ft_si_reject
(
BonjourData
*
bd
,
const
char
*
id
,
const
char
*
to
,
const
char
*
error_code
,
const
char
*
error_type
)
{
PurpleXmlNode
*
error_node
;
XepIq
*
iq
;
g_return_if_fail
(
error_code
!=
NULL
);
g_return_if_fail
(
error_type
!=
NULL
);
if
(
!
to
||
!
id
)
{
purple_debug_info
(
"bonjour"
,
"xep file transfer stream initialization error.
\n
"
);
return
;
}
iq
=
xep_iq_new
(
bd
,
XEP_IQ_ERROR
,
to
,
bonjour_get_jid
(
bd
->
xmpp_data
->
account
),
id
);
if
(
iq
==
NULL
)
return
;
error_node
=
purple_xmlnode_new_child
(
iq
->
node
,
"error"
);
purple_xmlnode_set_attrib
(
error_node
,
"code"
,
error_code
);
purple_xmlnode_set_attrib
(
error_node
,
"type"
,
error_type
);
/* TODO: Make this better */
if
(
purple_strequal
(
error_code
,
"403"
))
{
PurpleXmlNode
*
tmp_node
=
purple_xmlnode_new_child
(
error_node
,
"forbidden"
);
purple_xmlnode_set_namespace
(
tmp_node
,
"urn:ietf:params:xml:ns:xmpp-stanzas"
);
tmp_node
=
purple_xmlnode_new_child
(
error_node
,
"text"
);
purple_xmlnode_set_namespace
(
tmp_node
,
"urn:ietf:params:xml:ns:xmpp-stanzas"
);
purple_xmlnode_insert_data
(
tmp_node
,
"Offer Declined"
,
-1
);
}
else
if
(
purple_strequal
(
error_code
,
"404"
))
{
PurpleXmlNode
*
tmp_node
=
purple_xmlnode_new_child
(
error_node
,
"item-not-found"
);
purple_xmlnode_set_namespace
(
tmp_node
,
"urn:ietf:params:xml:ns:xmpp-stanzas"
);
}
xep_iq_send_and_free
(
iq
);
}
static
void
bonjour_xfer_cancel_send
(
PurpleXfer
*
xfer
)
{
purple_debug_info
(
"bonjour"
,
"Bonjour-xfer-cancel-send.
\n
"
);
}
static
void
bonjour_xfer_request_denied
(
PurpleXfer
*
xfer
)
{
XepXfer
*
xf
=
XEP_XFER
(
xfer
);
purple_debug_info
(
"bonjour"
,
"Bonjour-xfer-request-denied.
\n
"
);
if
(
xf
)
{
xep_ft_si_reject
(
xf
->
data
,
xf
->
sid
,
purple_xfer_get_remote_user
(
xfer
),
"403"
,
"cancel"
);
}
}
static
void
bonjour_xfer_cancel_recv
(
PurpleXfer
*
xfer
)
{
purple_debug_info
(
"bonjour"
,
"Bonjour-xfer-cancel-recv.
\n
"
);
}
struct
socket_cleanup
{
int
fd
;
guint
handle
;
};
static
void
_wait_for_socket_close
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
struct
socket_cleanup
*
sc
=
data
;
char
buf
[
1
];
int
ret
;
ret
=
recv
(
source
,
buf
,
1
,
0
);
if
(
ret
==
0
||
(
ret
==
-1
&&
!
(
errno
==
EAGAIN
||
errno
==
EWOULDBLOCK
)))
{
purple_debug_info
(
"bonjour"
,
"Client completed recieving; closing server socket.
\n
"
);
purple_input_remove
(
sc
->
handle
);
close
(
sc
->
fd
);
g_free
(
sc
);
}
}
static
void
bonjour_xfer_end
(
PurpleXfer
*
xfer
)
{
purple_debug_info
(
"bonjour"
,
"Bonjour-xfer-end.
\n
"
);
/* We can't allow the server side to close the connection until the client is complete,
* otherwise there is a RST resulting in an error on the client side */
if
(
purple_xfer_get_xfer_type
(
xfer
)
==
PURPLE_XFER_TYPE_SEND
&&
purple_xfer_is_completed
(
xfer
))
{
struct
socket_cleanup
*
sc
=
g_new0
(
struct
socket_cleanup
,
1
);
sc
->
fd
=
purple_xfer_get_fd
(
xfer
);
purple_xfer_set_fd
(
xfer
,
-1
);
sc
->
handle
=
purple_input_add
(
sc
->
fd
,
PURPLE_INPUT_READ
,
_wait_for_socket_close
,
sc
);
}
}
static
PurpleXfer
*
bonjour_si_xfer_find
(
BonjourData
*
bd
,
const
char
*
sid
,
const
char
*
from
)
{
GSList
*
xfers
;
PurpleXfer
*
xfer
;
XepXfer
*
xf
;
if
(
!
sid
||
!
from
||
!
bd
)
return
NULL
;
purple_debug_info
(
"bonjour"
,
"Look for sid=%s from=%s xferlists.
\n
"
,
sid
,
from
);
for
(
xfers
=
bd
->
xfer_lists
;
xfers
;
xfers
=
xfers
->
next
)
{
xfer
=
xfers
->
data
;
if
(
xfer
==
NULL
)
break
;
xf
=
XEP_XFER
(
xfer
);
if
(
xf
==
NULL
)
break
;
if
(
xf
->
sid
&&
purple_xfer_get_remote_user
(
xfer
)
&&
purple_strequal
(
xf
->
sid
,
sid
)
&&
purple_strequal
(
purple_xfer_get_remote_user
(
xfer
),
from
))
return
xfer
;
}
purple_debug_info
(
"bonjour"
,
"Look for xfer list fail
\n
"
);
return
NULL
;
}
static
void
xep_ft_si_offer
(
PurpleXfer
*
xfer
,
const
gchar
*
to
)
{
PurpleXmlNode
*
si_node
,
*
feature
,
*
field
,
*
file
,
*
x
;
XepIq
*
iq
;
XepXfer
*
xf
=
XEP_XFER
(
xfer
);
BonjourData
*
bd
=
NULL
;
char
buf
[
32
];
if
(
!
xf
)
return
;
bd
=
xf
->
data
;
if
(
!
bd
)
return
;
purple_debug_info
(
"bonjour"
,
"xep file transfer stream initialization offer-id=%d.
\n
"
,
next_id
);
/* Assign stream id. */
g_free
(
xf
->
iq_id
);
xf
->
iq_id
=
g_strdup_printf
(
"%u"
,
next_id
++
);
iq
=
xep_iq_new
(
xf
->
data
,
XEP_IQ_SET
,
to
,
bonjour_get_jid
(
bd
->
xmpp_data
->
account
),
xf
->
iq_id
);
if
(
iq
==
NULL
)
return
;
/*Construct Stream initialization offer message.*/
si_node
=
purple_xmlnode_new_child
(
iq
->
node
,
"si"
);
purple_xmlnode_set_namespace
(
si_node
,
"http://jabber.org/protocol/si"
);
purple_xmlnode_set_attrib
(
si_node
,
"profile"
,
"http://jabber.org/protocol/si/profile/file-transfer"
);
g_free
(
xf
->
sid
);
xf
->
sid
=
g_strdup
(
xf
->
iq_id
);
purple_xmlnode_set_attrib
(
si_node
,
"id"
,
xf
->
sid
);
file
=
purple_xmlnode_new_child
(
si_node
,
"file"
);
purple_xmlnode_set_namespace
(
file
,
"http://jabber.org/protocol/si/profile/file-transfer"
);
purple_xmlnode_set_attrib
(
file
,
"name"
,
purple_xfer_get_filename
(
xfer
));
g_snprintf
(
buf
,
sizeof
(
buf
),
"%"
G_GOFFSET_FORMAT
,
purple_xfer_get_size
(
xfer
));
purple_xmlnode_set_attrib
(
file
,
"size"
,
buf
);
feature
=
purple_xmlnode_new_child
(
si_node
,
"feature"
);
purple_xmlnode_set_namespace
(
feature
,
"http://jabber.org/protocol/feature-neg"
);
x
=
purple_xmlnode_new_child
(
feature
,
"x"
);
purple_xmlnode_set_namespace
(
x
,
"jabber:x:data"
);
purple_xmlnode_set_attrib
(
x
,
"type"
,
"form"
);
field
=
purple_xmlnode_new_child
(
x
,
"field"
);
purple_xmlnode_set_attrib
(
field
,
"var"
,
"stream-method"
);
purple_xmlnode_set_attrib
(
field
,
"type"
,
"list-single"
);
if
(
xf
->
mode
&
XEP_BYTESTREAMS
)
{
PurpleXmlNode
*
option
=
purple_xmlnode_new_child
(
field
,
"option"
);
PurpleXmlNode
*
value
=
purple_xmlnode_new_child
(
option
,
"value"
);
purple_xmlnode_insert_data
(
value
,
"http://jabber.org/protocol/bytestreams"
,
-1
);
}
if
(
xf
->
mode
&
XEP_IBB
)
{
PurpleXmlNode
*
option
=
purple_xmlnode_new_child
(
field
,
"option"
);
PurpleXmlNode
*
value
=
purple_xmlnode_new_child
(
option
,
"value"
);
purple_xmlnode_insert_data
(
value
,
"http://jabber.org/protocol/ibb"
,
-1
);
}
xep_iq_send_and_free
(
iq
);
}
static
void
xep_ft_si_result
(
PurpleXfer
*
xfer
,
const
char
*
to
)
{
PurpleXmlNode
*
si_node
,
*
feature
,
*
field
,
*
value
,
*
x
;
XepIq
*
iq
;
XepXfer
*
xf
;
BonjourData
*
bd
;
if
(
!
to
||
!
xfer
)
return
;
xf
=
XEP_XFER
(
xfer
);
bd
=
xf
->
data
;
purple_debug_info
(
"bonjour"
,
"xep file transfer stream initialization result.
\n
"
);
iq
=
xep_iq_new
(
bd
,
XEP_IQ_RESULT
,
to
,
bonjour_get_jid
(
bd
->
xmpp_data
->
account
),
xf
->
iq_id
);
if
(
iq
==
NULL
)
return
;
si_node
=
purple_xmlnode_new_child
(
iq
->
node
,
"si"
);
purple_xmlnode_set_namespace
(
si_node
,
"http://jabber.org/protocol/si"
);
/*purple_xmlnode_set_attrib(si_node, "profile", "http://jabber.org/protocol/si/profile/file-transfer");*/
feature
=
purple_xmlnode_new_child
(
si_node
,
"feature"
);
purple_xmlnode_set_namespace
(
feature
,
"http://jabber.org/protocol/feature-neg"
);
x
=
purple_xmlnode_new_child
(
feature
,
"x"
);
purple_xmlnode_set_namespace
(
x
,
"jabber:x:data"
);
purple_xmlnode_set_attrib
(
x
,
"type"
,
"submit"
);
field
=
purple_xmlnode_new_child
(
x
,
"field"
);
purple_xmlnode_set_attrib
(
field
,
"var"
,
"stream-method"
);
value
=
purple_xmlnode_new_child
(
field
,
"value"
);
purple_xmlnode_insert_data
(
value
,
"http://jabber.org/protocol/bytestreams"
,
-1
);
xep_iq_send_and_free
(
iq
);
}
/**
* Frees the whole tree of an xml node
*
* First determines the root of the xml tree and then frees the whole tree
* from there.
*
* @param node The node to free the tree from
*/
static
void
purple_xmlnode_free_tree
(
PurpleXmlNode
*
node
)
{
g_return_if_fail
(
node
!=
NULL
);
while
(
purple_xmlnode_get_parent
(
node
))
node
=
purple_xmlnode_get_parent
(
node
);
purple_xmlnode_free
(
node
);
}
PurpleXfer
*
bonjour_new_xfer
(
PurpleProtocolXfer
*
prplxfer
,
PurpleConnection
*
gc
,
const
char
*
who
)
{
PurpleXfer
*
xfer
;
XepXfer
*
xep_xfer
;
BonjourData
*
bd
;
if
(
who
==
NULL
||
gc
==
NULL
)
return
NULL
;
purple_debug_info
(
"bonjour"
,
"Bonjour-new-xfer to %s.
\n
"
,
who
);
bd
=
purple_connection_get_protocol_data
(
gc
);
if
(
bd
==
NULL
)
return
NULL
;
/* Build the file transfer handle */
xep_xfer
=
g_object_new
(
XEP_TYPE_XFER
,
"account"
,
purple_connection_get_account
(
gc
),
"type"
,
PURPLE_XFER_TYPE_SEND
,
"remote-user"
,
who
,
NULL
);
xfer
=
PURPLE_XFER
(
xep_xfer
);
xep_xfer
->
data
=
bd
;
purple_debug_info
(
"bonjour"
,
"Bonjour-new-xfer bd=%p data=%p.
\n
"
,
bd
,
xep_xfer
->
data
);
/* We don't support IBB yet */
/*xep_xfer->mode = XEP_BYTESTREAMS | XEP_IBB;*/
xep_xfer
->
mode
=
XEP_BYTESTREAMS
;
xep_xfer
->
sid
=
NULL
;
bd
->
xfer_lists
=
g_slist_append
(
bd
->
xfer_lists
,
xfer
);
return
xfer
;
}
void
bonjour_send_file
(
PurpleProtocolXfer
*
prplxfer
,
PurpleConnection
*
gc
,
const
char
*
who
,
const
char
*
file
)
{
PurpleXfer
*
xfer
;
g_return_if_fail
(
gc
!=
NULL
);
g_return_if_fail
(
who
!=
NULL
);
purple_debug_info
(
"bonjour"
,
"Bonjour-send-file to=%s.
\n
"
,
who
);
xfer
=
bonjour_new_xfer
(
prplxfer
,
gc
,
who
);
if
(
file
)
purple_xfer_request_accepted
(
xfer
,
file
);
else
purple_xfer_request
(
xfer
);
}
static
void
bonjour_xfer_init
(
PurpleXfer
*
xfer
)
{
PurpleBuddy
*
buddy
;
BonjourBuddy
*
bb
;
XepXfer
*
xf
;
xf
=
XEP_XFER
(
xfer
);
purple_debug_info
(
"bonjour"
,
"Bonjour-xfer-init.
\n
"
);
buddy
=
purple_blist_find_buddy
(
purple_xfer_get_account
(
xfer
),
purple_xfer_get_remote_user
(
xfer
));
/* this buddy is offline. */
if
(
buddy
==
NULL
||
(
bb
=
purple_buddy_get_protocol_data
(
buddy
))
==
NULL
)
return
;
/* Assume it is the first IP. We could do something like keep track of which one is in use or something. */
if
(
bb
->
ips
)
xf
->
buddy_ip
=
g_strdup
(
bb
->
ips
->
data
);
if
(
purple_xfer_get_xfer_type
(
xfer
)
==
PURPLE_XFER_TYPE_SEND
)
{
/* initiate file transfer, send SI offer. */
purple_debug_info
(
"bonjour"
,
"Bonjour xfer type is PURPLE_XFER_TYPE_SEND.
\n
"
);
xep_ft_si_offer
(
xfer
,
purple_xfer_get_remote_user
(
xfer
));
}
else
{
/* accept file transfer request, send SI result. */
xep_ft_si_result
(
xfer
,
purple_xfer_get_remote_user
(
xfer
));
purple_debug_info
(
"bonjour"
,
"Bonjour xfer type is PURPLE_XFER_TYPE_RECEIVE.
\n
"
);
}
}
void
xep_si_parse
(
PurpleConnection
*
pc
,
PurpleXmlNode
*
packet
,
PurpleBuddy
*
pb
)
{
const
char
*
type
,
*
id
;
BonjourData
*
bd
;
PurpleXfer
*
xfer
;
const
gchar
*
name
=
NULL
;
g_return_if_fail
(
pc
!=
NULL
);
g_return_if_fail
(
packet
!=
NULL
);
g_return_if_fail
(
pb
!=
NULL
);
bd
=
purple_connection_get_protocol_data
(
pc
);
if
(
bd
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"xep-si-parse.
\n
"
);
name
=
purple_buddy_get_name
(
pb
);
type
=
purple_xmlnode_get_attrib
(
packet
,
"type"
);
id
=
purple_xmlnode_get_attrib
(
packet
,
"id"
);
if
(
!
type
)
return
;
if
(
purple_strequal
(
type
,
"set"
))
{
PurpleXmlNode
*
si
;
gboolean
parsed_receive
=
FALSE
;
si
=
purple_xmlnode_get_child
(
packet
,
"si"
);
purple_debug_info
(
"bonjour"
,
"si offer Message type - SET.
\n
"
);
if
(
si
)
{
const
char
*
profile
;
profile
=
purple_xmlnode_get_attrib
(
si
,
"profile"
);
if
(
purple_strequal
(
profile
,
"http://jabber.org/protocol/si/profile/file-transfer"
))
{
const
char
*
filename
=
NULL
,
*
filesize_str
=
NULL
;
goffset
filesize
=
0
;
PurpleXmlNode
*
file
;
const
char
*
sid
=
purple_xmlnode_get_attrib
(
si
,
"id"
);
if
((
file
=
purple_xmlnode_get_child
(
si
,
"file"
)))
{
filename
=
purple_xmlnode_get_attrib
(
file
,
"name"
);
if
((
filesize_str
=
purple_xmlnode_get_attrib
(
file
,
"size"
)))
filesize
=
g_ascii_strtoll
(
filesize_str
,
NULL
,
10
);
}
/* TODO: Make sure that it is advertising a bytestreams transfer */
if
(
filename
)
{
bonjour_xfer_receive
(
pc
,
id
,
sid
,
name
,
filesize
,
filename
,
XEP_BYTESTREAMS
);
parsed_receive
=
TRUE
;
}
}
}
if
(
!
parsed_receive
)
{
BonjourData
*
bd
=
purple_connection_get_protocol_data
(
pc
);
purple_debug_info
(
"bonjour"
,
"rejecting unrecognized si SET offer.
\n
"
);
xep_ft_si_reject
(
bd
,
id
,
name
,
"403"
,
"cancel"
);
/*TODO: Send Cancel (501) */
}
}
else
if
(
purple_strequal
(
type
,
"result"
))
{
purple_debug_info
(
"bonjour"
,
"si offer Message type - RESULT.
\n
"
);
xfer
=
bonjour_si_xfer_find
(
bd
,
id
,
name
);
if
(
xfer
==
NULL
)
{
BonjourData
*
bd
=
purple_connection_get_protocol_data
(
pc
);
purple_debug_info
(
"bonjour"
,
"xfer find fail.
\n
"
);
xep_ft_si_reject
(
bd
,
id
,
name
,
"403"
,
"cancel"
);
}
else
bonjour_bytestreams_init
(
xfer
);
}
else
if
(
purple_strequal
(
type
,
"error"
))
{
purple_debug_info
(
"bonjour"
,
"si offer Message type - ERROR.
\n
"
);
xfer
=
bonjour_si_xfer_find
(
bd
,
id
,
name
);
if
(
xfer
==
NULL
)
purple_debug_info
(
"bonjour"
,
"xfer find fail.
\n
"
);
else
purple_xfer_cancel_remote
(
xfer
);
}
else
purple_debug_info
(
"bonjour"
,
"si offer Message type - Unknown-%s.
\n
"
,
type
);
}
/**
* Will compare a host with a buddy_ip.
*
* Additionally to a common 'purple_strequal(host, buddy_ip)', it will also return TRUE
* if 'host' is a link local IPv6 address without an appended interface
* identifier and 'buddy_ip' string is "host" + "%iface".
*
* Note: This may theoretically result in the attempt to connect to the wrong
* host, because we do not know for sure which interface the according link
* local IPv6 address might relate to and RFC4862 for instance only ensures the
* uniqueness of this address on a given link. So we could possibly have two
* distinct buddies with the same ipv6 link local address on two distinct
* interfaces. Unfortunately XEP-0065 does not seem to specify how to deal with
* link local ip addresses properly...
* However, in practice the possiblity for such a conflict is relatively low
* (2011 - might be different in the future though?).
*
* @param host ipv4 or ipv6 address string
* @param buddy_ip ipv4 or ipv6 address string
* @return TRUE if they match, FALSE otherwise
*/
static
gboolean
xep_cmp_addr
(
const
char
*
host
,
const
char
*
buddy_ip
)
{
#if defined(AF_INET6) && defined(HAVE_GETADDRINFO)
struct
addrinfo
hint
,
*
res
=
NULL
;
common_sockaddr_t
addr
;
int
ret
;
memset
(
&
hint
,
0
,
sizeof
(
hint
));
hint
.
ai_family
=
AF_UNSPEC
;
hint
.
ai_flags
=
AI_NUMERICHOST
;
ret
=
getaddrinfo
(
host
,
NULL
,
&
hint
,
&
res
);
if
(
ret
)
goto
out
;
memcpy
(
&
addr
,
res
->
ai_addr
,
sizeof
(
addr
));
if
(
res
->
ai_family
!=
AF_INET6
||
!
IN6_IS_ADDR_LINKLOCAL
(
&
addr
.
in6
.
sin6_addr
))
{
freeaddrinfo
(
res
);
goto
out
;
}
freeaddrinfo
(
res
);
if
(
strlen
(
buddy_ip
)
<=
strlen
(
host
)
||
buddy_ip
[
strlen
(
host
)]
!=
'%'
)
return
FALSE
;
return
!
strncmp
(
host
,
buddy_ip
,
strlen
(
host
));
out
:
#endif
return
purple_strequal
(
host
,
buddy_ip
);
}
static
inline
gint
xep_addr_differ
(
const
char
*
buddy_ip
,
const
char
*
host
)
{
return
!
xep_cmp_addr
(
host
,
buddy_ip
);
}
/**
* Create and insert an identical twin
*
* Creates a copy of the specified node and inserts it right after
* this original node.
*
* @param node The node to clone
* @return A pointer to the new, cloned twin if successful
* or NULL otherwise.
*/
static
PurpleXmlNode
*
purple_xmlnode_insert_twin_copy
(
PurpleXmlNode
*
node
)
{
PurpleXmlNode
*
copy
;
g_return_val_if_fail
(
node
!=
NULL
,
NULL
);
copy
=
purple_xmlnode_copy
(
node
);
g_return_val_if_fail
(
copy
!=
NULL
,
NULL
);
copy
->
next
=
node
->
next
;
node
->
next
=
copy
;
return
copy
;
}
/**
* Tries to append an interface scope to an IPv6 link local address.
*
* If the given address is a link local IPv6 address (with no
* interface scope) then we try to determine all fitting interfaces
* from our Bonjour IP address list.
*
* For any such found matches we insert a copy of our current xml
* streamhost entry right after this streamhost entry and append
* the determined interface to the host address of this copy.
*
* @param cur_streamhost The XML streamhost node we examine
* @param host The host address to examine in text form
* @param pb Buddy to get the list of link local IPv6 addresses
* and their interface from
* @return Returns TRUE if the specified 'host' address is a
* link local IPv6 address with no interface scope.
* Otherwise returns FALSE.
*/
static
gboolean
add_ipv6_link_local_ifaces
(
PurpleXmlNode
*
cur_streamhost
,
const
char
*
host
,
PurpleBuddy
*
pb
)
{
PurpleXmlNode
*
new_streamhost
=
NULL
;
struct
in6_addr
in6_addr
;
BonjourBuddy
*
bb
;
GSList
*
ip_elem
;
if
(
inet_pton
(
AF_INET6
,
host
,
&
in6_addr
)
!=
1
||
!
IN6_IS_ADDR_LINKLOCAL
(
&
in6_addr
)
||
strchr
(
host
,
'%'
))
return
FALSE
;
bb
=
purple_buddy_get_protocol_data
(
pb
);
for
(
ip_elem
=
bb
->
ips
;
(
ip_elem
=
g_slist_find_custom
(
ip_elem
,
host
,
(
GCompareFunc
)
&
xep_addr_differ
));
ip_elem
=
ip_elem
->
next
)
{
purple_debug_info
(
"bonjour"
,
"Inserting an PurpleXmlNode twin copy for %s with new host address %s
\n
"
,
host
,
(
char
*
)
ip_elem
->
data
);
new_streamhost
=
purple_xmlnode_insert_twin_copy
(
cur_streamhost
);
purple_xmlnode_set_attrib
(
new_streamhost
,
"host"
,
ip_elem
->
data
);
}
if
(
!
new_streamhost
)
purple_debug_info
(
"bonjour"
,
"No interface for this IPv6 link local address found: %s
\n
"
,
host
);
return
TRUE
;
}
static
gboolean
__xep_bytestreams_parse
(
PurpleBuddy
*
pb
,
PurpleXfer
*
xfer
,
PurpleXmlNode
*
streamhost
,
const
char
*
iq_id
)
{
char
*
tmp_iq_id
;
const
char
*
jid
,
*
host
,
*
port
;
int
portnum
;
XepXfer
*
xf
=
XEP_XFER
(
xfer
);
for
(;
streamhost
;
streamhost
=
purple_xmlnode_get_next_twin
(
streamhost
))
{
if
(
!
(
jid
=
purple_xmlnode_get_attrib
(
streamhost
,
"jid"
))
||
!
(
host
=
purple_xmlnode_get_attrib
(
streamhost
,
"host"
))
||
!
(
port
=
purple_xmlnode_get_attrib
(
streamhost
,
"port"
))
||
!
(
portnum
=
atoi
(
port
)))
{
purple_debug_info
(
"bonjour"
,
"bytestream offer Message parse error.
\n
"
);
continue
;
}
/* skip IPv6 link local addresses with no interface scope
* (but try to add a new one with an interface scope then) */
if
(
add_ipv6_link_local_ifaces
(
streamhost
,
host
,
pb
))
continue
;
tmp_iq_id
=
g_strdup
(
iq_id
);
g_free
(
xf
->
iq_id
);
g_free
(
xf
->
jid
);
g_free
(
xf
->
proxy_host
);
xf
->
iq_id
=
tmp_iq_id
;
xf
->
jid
=
g_strdup
(
jid
);
xf
->
proxy_host
=
g_strdup
(
host
);
xf
->
proxy_port
=
portnum
;
xf
->
streamhost
=
streamhost
;
xf
->
pb
=
pb
;
purple_debug_info
(
"bonjour"
,
"bytestream offer parse"
"jid=%s host=%s port=%d.
\n
"
,
jid
,
host
,
portnum
);
bonjour_bytestreams_connect
(
xfer
);
return
TRUE
;
}
return
FALSE
;
}
void
xep_bytestreams_parse
(
PurpleConnection
*
pc
,
PurpleXmlNode
*
packet
,
PurpleBuddy
*
pb
)
{
const
char
*
type
,
*
from
,
*
iq_id
,
*
sid
;
PurpleXmlNode
*
query
,
*
streamhost
;
BonjourData
*
bd
;
PurpleXfer
*
xfer
;
g_return_if_fail
(
pc
!=
NULL
);
g_return_if_fail
(
packet
!=
NULL
);
g_return_if_fail
(
pb
!=
NULL
);
bd
=
purple_connection_get_protocol_data
(
pc
);
if
(
bd
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"xep-bytestreams-parse.
\n
"
);
type
=
purple_xmlnode_get_attrib
(
packet
,
"type"
);
from
=
purple_buddy_get_name
(
pb
);
query
=
purple_xmlnode_get_child
(
packet
,
"query"
);
if
(
!
type
)
return
;
query
=
purple_xmlnode_copy
(
query
);
if
(
!
query
)
return
;
if
(
!
purple_strequal
(
type
,
"set"
))
{
purple_debug_info
(
"bonjour"
,
"bytestream offer Message type - Unknown-%s.
\n
"
,
type
);
return
;
}
purple_debug_info
(
"bonjour"
,
"bytestream offer Message type - SET.
\n
"
);
iq_id
=
purple_xmlnode_get_attrib
(
packet
,
"id"
);
sid
=
purple_xmlnode_get_attrib
(
query
,
"sid"
);
xfer
=
bonjour_si_xfer_find
(
bd
,
sid
,
from
);
streamhost
=
purple_xmlnode_get_child
(
query
,
"streamhost"
);
if
(
xfer
&&
streamhost
&&
__xep_bytestreams_parse
(
pb
,
xfer
,
streamhost
,
iq_id
))
return
;
/* success */
purple_debug_error
(
"bonjour"
,
"Didn't find an acceptable streamhost.
\n
"
);
if
(
iq_id
&&
xfer
!=
NULL
)
xep_ft_si_reject
(
bd
,
iq_id
,
purple_xfer_get_remote_user
(
xfer
),
"404"
,
"cancel"
);
}
static
void
bonjour_xfer_receive
(
PurpleConnection
*
pc
,
const
char
*
id
,
const
char
*
sid
,
const
char
*
from
,
goffset
filesize
,
const
char
*
filename
,
int
option
)
{
PurpleXfer
*
xfer
;
XepXfer
*
xf
;
BonjourData
*
bd
;
if
(
pc
==
NULL
||
id
==
NULL
||
from
==
NULL
)
return
;
bd
=
purple_connection_get_protocol_data
(
pc
);
if
(
bd
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"bonjour-xfer-receive.
\n
"
);
/* Build the file transfer handle */
xf
=
g_object_new
(
XEP_TYPE_XFER
,
"account"
,
purple_connection_get_account
(
pc
),
"type"
,
PURPLE_XFER_TYPE_RECEIVE
,
"remote-user"
,
from
,
NULL
);
xfer
=
PURPLE_XFER
(
xf
);
xf
->
data
=
bd
;
purple_xfer_set_filename
(
xfer
,
filename
);
xf
->
iq_id
=
g_strdup
(
id
);
xf
->
sid
=
g_strdup
(
sid
);
if
(
filesize
>
0
)
purple_xfer_set_size
(
xfer
,
filesize
);
bd
->
xfer_lists
=
g_slist_append
(
bd
->
xfer_lists
,
xfer
);
purple_xfer_request
(
xfer
);
}
static
void
bonjour_sock5_request_cb
(
gpointer
data
,
gint
source
,
PurpleInputCondition
cond
)
{
PurpleXfer
*
xfer
=
data
;
XepXfer
*
xf
=
XEP_XFER
(
xfer
);
int
acceptfd
;
int
len
=
0
;
if
(
xf
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"bonjour_sock5_request_cb - req_state = 0x%x
\n
"
,
xf
->
sock5_req_state
);
switch
(
xf
->
sock5_req_state
){
case
0x00
:
acceptfd
=
accept
(
source
,
NULL
,
0
);
if
(
acceptfd
==
-1
&&
(
errno
==
EAGAIN
||
errno
==
EWOULDBLOCK
))
{
}
else
if
(
acceptfd
==
-1
)
{
/* This should cancel the ft */
purple_debug_error
(
"bonjour"
,
"Error accepting incoming SOCKS5 connection. (%d)
\n
"
,
errno
);
close
(
source
);
purple_xfer_cancel_remote
(
xfer
);
return
;
}
else
{
purple_debug_info
(
"bonjour"
,
"Accepted SOCKS5 ft connection - fd=%d
\n
"
,
acceptfd
);
_purple_network_set_common_socket_flags
(
acceptfd
);
purple_input_remove
(
purple_xfer_get_watcher
(
xfer
));
close
(
source
);
purple_xfer_set_watcher
(
xfer
,
purple_input_add
(
acceptfd
,
PURPLE_INPUT_READ
,
bonjour_sock5_request_cb
,
xfer
));
xf
->
sock5_req_state
++
;
xf
->
rxlen
=
0
;
}
break
;
case
0x01
:
purple_xfer_set_fd
(
xfer
,
source
);
len
=
read
(
source
,
xf
->
rx_buf
+
xf
->
rxlen
,
3
);
if
(
len
<
0
&&
errno
==
EAGAIN
)
return
;
else
if
(
len
<=
0
){
purple_xfer_cancel_remote
(
xfer
);
return
;
}
else
{
purple_input_remove
(
purple_xfer_get_watcher
(
xfer
));
purple_xfer_set_watcher
(
xfer
,
purple_input_add
(
source
,
PURPLE_INPUT_WRITE
,
bonjour_sock5_request_cb
,
xfer
));
xf
->
sock5_req_state
++
;
xf
->
rxlen
=
0
;
bonjour_sock5_request_cb
(
xfer
,
source
,
PURPLE_INPUT_WRITE
);
}
break
;
case
0x02
:
xf
->
tx_buf
[
0
]
=
0x05
;
xf
->
tx_buf
[
1
]
=
0x00
;
len
=
write
(
source
,
xf
->
tx_buf
,
2
);
if
(
len
<
0
&&
errno
==
EAGAIN
)
return
;
else
if
(
len
<
0
)
{
close
(
source
);
purple_xfer_cancel_remote
(
xfer
);
return
;
}
else
{
purple_input_remove
(
purple_xfer_get_watcher
(
xfer
));
purple_xfer_set_watcher
(
xfer
,
purple_input_add
(
source
,
PURPLE_INPUT_READ
,
bonjour_sock5_request_cb
,
xfer
));
xf
->
sock5_req_state
++
;
xf
->
rxlen
=
0
;
}
break
;
case
0x03
:
len
=
read
(
source
,
xf
->
rx_buf
+
xf
->
rxlen
,
20
);
if
(
len
<=
0
){
}
else
{
purple_input_remove
(
purple_xfer_get_watcher
(
xfer
));
purple_xfer_set_watcher
(
xfer
,
purple_input_add
(
source
,
PURPLE_INPUT_WRITE
,
bonjour_sock5_request_cb
,
xfer
));
xf
->
sock5_req_state
++
;
xf
->
rxlen
=
0
;
bonjour_sock5_request_cb
(
xfer
,
source
,
PURPLE_INPUT_WRITE
);
}
break
;
case
0x04
:
xf
->
tx_buf
[
0
]
=
0x05
;
xf
->
tx_buf
[
1
]
=
0x00
;
xf
->
tx_buf
[
2
]
=
0x00
;
xf
->
tx_buf
[
3
]
=
0x03
;
xf
->
tx_buf
[
4
]
=
strlen
(
xf
->
buddy_ip
);
memcpy
(
xf
->
tx_buf
+
5
,
xf
->
buddy_ip
,
strlen
(
xf
->
buddy_ip
));
xf
->
tx_buf
[
5
+
strlen
(
xf
->
buddy_ip
)]
=
0x00
;
xf
->
tx_buf
[
6
+
strlen
(
xf
->
buddy_ip
)]
=
0x00
;
len
=
write
(
source
,
xf
->
tx_buf
,
7
+
strlen
(
xf
->
buddy_ip
));
if
(
len
<
0
&&
errno
==
EAGAIN
)
{
return
;
}
else
if
(
len
<
0
)
{
close
(
source
);
purple_xfer_cancel_remote
(
xfer
);
return
;
}
else
{
purple_input_remove
(
purple_xfer_get_watcher
(
xfer
));
purple_xfer_set_watcher
(
xfer
,
0
);
xf
->
rxlen
=
0
;
/*close(source);*/
purple_xfer_start
(
xfer
,
source
,
NULL
,
-1
);
}
break
;
default
:
break
;
}
}
static
void
bonjour_bytestreams_listen
(
int
sock
,
gpointer
data
)
{
PurpleXfer
*
xfer
=
data
;
XepXfer
*
xf
;
XepIq
*
iq
;
PurpleXmlNode
*
query
,
*
streamhost
;
gchar
*
port
;
GSList
*
local_ips
;
BonjourData
*
bd
;
purple_debug_info
(
"bonjour"
,
"Bonjour-bytestreams-listen. sock=%d.
\n
"
,
sock
);
if
(
sock
<
0
||
xfer
==
NULL
)
{
/*purple_xfer_cancel_local(xfer);*/
return
;
}
purple_xfer_set_watcher
(
xfer
,
purple_input_add
(
sock
,
PURPLE_INPUT_READ
,
bonjour_sock5_request_cb
,
xfer
));
xf
=
XEP_XFER
(
xfer
);
xf
->
listen_data
=
NULL
;
bd
=
xf
->
data
;
iq
=
xep_iq_new
(
bd
,
XEP_IQ_SET
,
purple_xfer_get_remote_user
(
xfer
),
bonjour_get_jid
(
bd
->
xmpp_data
->
account
),
xf
->
sid
);
query
=
purple_xmlnode_new_child
(
iq
->
node
,
"query"
);
purple_xmlnode_set_namespace
(
query
,
"http://jabber.org/protocol/bytestreams"
);
purple_xmlnode_set_attrib
(
query
,
"sid"
,
xf
->
sid
);
purple_xmlnode_set_attrib
(
query
,
"mode"
,
"tcp"
);
purple_xfer_set_local_port
(
xfer
,
purple_network_get_port_from_fd
(
sock
));
local_ips
=
bonjour_xmpp_get_local_ips
(
sock
);
port
=
g_strdup_printf
(
"%hu"
,
purple_xfer_get_local_port
(
xfer
));
while
(
local_ips
)
{
streamhost
=
purple_xmlnode_new_child
(
query
,
"streamhost"
);
purple_xmlnode_set_attrib
(
streamhost
,
"jid"
,
xf
->
sid
);
purple_xmlnode_set_attrib
(
streamhost
,
"host"
,
local_ips
->
data
);
purple_xmlnode_set_attrib
(
streamhost
,
"port"
,
port
);
g_free
(
local_ips
->
data
);
local_ips
=
g_slist_delete_link
(
local_ips
,
local_ips
);
}
g_free
(
port
);
xep_iq_send_and_free
(
iq
);
}
static
void
bonjour_bytestreams_init
(
PurpleXfer
*
xfer
)
{
XepXfer
*
xf
;
if
(
xfer
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"Bonjour-bytestreams-init.
\n
"
);
xf
=
XEP_XFER
(
xfer
);
xf
->
listen_data
=
purple_network_listen_range
(
0
,
0
,
AF_UNSPEC
,
SOCK_STREAM
,
FALSE
,
bonjour_bytestreams_listen
,
xfer
);
if
(
xf
->
listen_data
==
NULL
)
purple_xfer_cancel_local
(
xfer
);
}
static
void
bonjour_bytestreams_connect_cb
(
gpointer
data
,
gint
source
,
const
gchar
*
error_message
)
{
PurpleXfer
*
xfer
=
data
;
XepXfer
*
xf
=
XEP_XFER
(
xfer
);
XepIq
*
iq
;
PurpleXmlNode
*
q_node
,
*
tmp_node
;
BonjourData
*
bd
;
gboolean
ret
=
FALSE
;
xf
->
proxy_connection
=
NULL
;
if
(
source
<
0
)
{
purple_debug_error
(
"bonjour"
,
"Error connecting via SOCKS5 to %s - %s
\n
"
,
xf
->
proxy_host
,
error_message
?
error_message
:
"(null)"
);
tmp_node
=
purple_xmlnode_get_next_twin
(
xf
->
streamhost
);
ret
=
__xep_bytestreams_parse
(
xf
->
pb
,
xfer
,
tmp_node
,
xf
->
iq_id
);
if
(
!
ret
)
{
xep_ft_si_reject
(
xf
->
data
,
xf
->
iq_id
,
purple_xfer_get_remote_user
(
xfer
),
"404"
,
"cancel"
);
/* Cancel the connection */
purple_xfer_cancel_local
(
xfer
);
}
return
;
}
purple_debug_info
(
"bonjour"
,
"Connected successfully via SOCKS5, starting transfer.
\n
"
);
bd
=
xf
->
data
;
/* Here, start the file transfer.*/
/* Notify Initiator of Connection */
iq
=
xep_iq_new
(
bd
,
XEP_IQ_RESULT
,
purple_xfer_get_remote_user
(
xfer
),
bonjour_get_jid
(
bd
->
xmpp_data
->
account
),
xf
->
iq_id
);
q_node
=
purple_xmlnode_new_child
(
iq
->
node
,
"query"
);
purple_xmlnode_set_namespace
(
q_node
,
"http://jabber.org/protocol/bytestreams"
);
tmp_node
=
purple_xmlnode_new_child
(
q_node
,
"streamhost-used"
);
purple_xmlnode_set_attrib
(
tmp_node
,
"jid"
,
xf
->
jid
);
xep_iq_send_and_free
(
iq
);
purple_xfer_start
(
xfer
,
source
,
NULL
,
-1
);
}
static
void
bonjour_bytestreams_connect
(
PurpleXfer
*
xfer
)
{
PurpleBuddy
*
pb
;
PurpleAccount
*
account
=
NULL
;
GChecksum
*
hash
;
XepXfer
*
xf
;
char
dstaddr
[
41
];
const
gchar
*
name
=
NULL
;
unsigned
char
hashval
[
20
];
gsize
digest_len
=
20
;
char
*
p
;
int
i
;
if
(
xfer
==
NULL
)
return
;
purple_debug_info
(
"bonjour"
,
"bonjour-bytestreams-connect.
\n
"
);
xf
=
XEP_XFER
(
xfer
);
pb
=
xf
->
pb
;
name
=
purple_buddy_get_name
(
pb
);
account
=
purple_buddy_get_account
(
pb
);
p
=
g_strdup_printf
(
"%s%s%s"
,
xf
->
sid
,
name
,
bonjour_get_jid
(
account
));
hash
=
g_checksum_new
(
G_CHECKSUM_SHA1
);
g_checksum_update
(
hash
,
(
guchar
*
)
p
,
-1
);
g_checksum_get_digest
(
hash
,
hashval
,
&
digest_len
);
g_checksum_free
(
hash
);
g_free
(
p
);
memset
(
dstaddr
,
0
,
41
);
p
=
dstaddr
;
for
(
i
=
0
;
i
<
20
;
i
++
,
p
+=
2
)
{
g_snprintf
(
p
,
3
,
"%02x"
,
hashval
[
i
]);
}
xf
->
proxy_info
=
purple_proxy_info_new
();
purple_proxy_info_set_proxy_type
(
xf
->
proxy_info
,
PURPLE_PROXY_SOCKS5
);
purple_proxy_info_set_host
(
xf
->
proxy_info
,
xf
->
proxy_host
);
purple_proxy_info_set_port
(
xf
->
proxy_info
,
xf
->
proxy_port
);
xf
->
proxy_connection
=
purple_proxy_connect_socks5_account
(
purple_account_get_connection
(
account
),
account
,
xf
->
proxy_info
,
dstaddr
,
0
,
bonjour_bytestreams_connect_cb
,
xfer
);
if
(
xf
->
proxy_connection
==
NULL
)
{
xep_ft_si_reject
(
xf
->
data
,
xf
->
iq_id
,
purple_xfer_get_remote_user
(
xfer
),
"404"
,
"cancel"
);
/* Cancel the connection */
purple_xfer_cancel_local
(
xfer
);
}
}
static
void
xep_xfer_init
(
XepXfer
*
xfer
)
{
}
static
void
xep_xfer_finalize
(
GObject
*
obj
)
{
XepXfer
*
xf
=
XEP_XFER
(
obj
);
BonjourData
*
bd
=
(
BonjourData
*
)
xf
->
data
;
if
(
bd
!=
NULL
)
{
bd
->
xfer_lists
=
g_slist_remove
(
bd
->
xfer_lists
,
PURPLE_XFER
(
xf
));
purple_debug_misc
(
"bonjour"
,
"B free xfer from lists(%p).
\n
"
,
bd
->
xfer_lists
);
}
if
(
xf
->
proxy_connection
!=
NULL
)
{
purple_proxy_connect_cancel
(
xf
->
proxy_connection
);
}
if
(
xf
->
proxy_info
!=
NULL
)
{
purple_proxy_info_destroy
(
xf
->
proxy_info
);
}
if
(
xf
->
listen_data
!=
NULL
)
{
purple_network_listen_cancel
(
xf
->
listen_data
);
}
g_free
(
xf
->
iq_id
);
g_free
(
xf
->
jid
);
g_free
(
xf
->
proxy_host
);
g_free
(
xf
->
buddy_ip
);
g_free
(
xf
->
sid
);
g_clear_pointer
(
&
xf
->
streamhost
,
purple_xmlnode_free_tree
);
G_OBJECT_CLASS
(
xep_xfer_parent_class
)
->
finalize
(
obj
);
}
static
void
xep_xfer_class_finalize
(
XepXferClass
*
klass
)
{
}
static
void
xep_xfer_class_init
(
XepXferClass
*
klass
)
{
GObjectClass
*
obj_class
=
G_OBJECT_CLASS
(
klass
);
PurpleXferClass
*
xfer_class
=
PURPLE_XFER_CLASS
(
klass
);
obj_class
->
finalize
=
xep_xfer_finalize
;
xfer_class
->
init
=
bonjour_xfer_init
;
xfer_class
->
request_denied
=
bonjour_xfer_request_denied
;
xfer_class
->
cancel_recv
=
bonjour_xfer_cancel_recv
;
xfer_class
->
cancel_send
=
bonjour_xfer_cancel_send
;
xfer_class
->
end
=
bonjour_xfer_end
;
}
void
xep_xfer_register
(
GTypeModule
*
module
)
{
xep_xfer_register_type
(
module
);
}