pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
More error logging, please.
release-2.x.y
2014-01-18, Mark Doliner
956f247148db
More error logging, please.
/**
* @file slplink.c MSNSLP Link support
*
* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
"internal.h"
#include
"debug.h"
#include
"msn.h"
#include
"slplink.h"
#include
"slpmsg_part.h"
#include
"sbconn.h"
#include
"switchboard.h"
#include
"slp.h"
#include
"p2p.h"
#ifdef MSN_DEBUG_SLP_FILES
static
int
m_sc
=
0
;
static
int
m_rc
=
0
;
static
void
debug_part_to_file
(
MsnSlpMessage
*
msg
,
gboolean
send
)
{
char
*
tmp
;
char
*
dir
;
char
*
data
;
int
c
;
gsize
data_size
;
dir
=
send
?
"send"
:
"recv"
;
c
=
send
?
m_sc
++
:
m_rc
++
;
tmp
=
g_strdup_printf
(
"%s/msntest/%s/%03d"
,
purple_user_dir
(),
dir
,
c
);
data
=
msn_slpmsg_serialize
(
msg
,
&
data_size
);
if
(
!
purple_util_write_data_to_file_absolute
(
tmp
,
data
,
data_size
))
{
purple_debug_error
(
"msn"
,
"could not save debug file
\n
"
);
}
g_free
(
tmp
);
}
#endif
/**************************************************************************
* Main
**************************************************************************/
static
MsnSlpLink
*
msn_slplink_new
(
MsnSession
*
session
,
const
char
*
username
)
{
MsnSlpLink
*
slplink
;
g_return_val_if_fail
(
session
!=
NULL
,
NULL
);
slplink
=
g_new0
(
MsnSlpLink
,
1
);
if
(
purple_debug_is_verbose
())
purple_debug_info
(
"msn"
,
"slplink_new: slplink(%p)
\n
"
,
slplink
);
slplink
->
session
=
session
;
slplink
->
slp_seq_id
=
rand
()
%
0xFFFFFF00
+
4
;
slplink
->
remote_user
=
g_strdup
(
username
);
slplink
->
p2p_version
=
MSN_P2P_VERSION_ONE
;
slplink
->
slp_msg_queue
=
g_queue_new
();
session
->
slplinks
=
g_list_append
(
session
->
slplinks
,
slplink
);
return
msn_slplink_ref
(
slplink
);
}
static
void
msn_slplink_destroy
(
MsnSlpLink
*
slplink
)
{
MsnSession
*
session
;
if
(
purple_debug_is_verbose
())
purple_debug_info
(
"msn"
,
"slplink_destroy: slplink(%p)
\n
"
,
slplink
);
if
(
slplink
->
swboard
!=
NULL
)
{
slplink
->
swboard
->
slplinks
=
g_list_remove
(
slplink
->
swboard
->
slplinks
,
slplink
);
slplink
->
swboard
=
NULL
;
}
session
=
slplink
->
session
;
if
(
slplink
->
dc
!=
NULL
)
{
slplink
->
dc
->
slplink
=
NULL
;
msn_dc_destroy
(
slplink
->
dc
);
slplink
->
dc
=
NULL
;
}
while
(
slplink
->
slp_calls
!=
NULL
)
msn_slpcall_destroy
(
slplink
->
slp_calls
->
data
);
g_queue_free
(
slplink
->
slp_msg_queue
);
session
->
slplinks
=
g_list_remove
(
session
->
slplinks
,
slplink
);
g_free
(
slplink
->
remote_user
);
g_free
(
slplink
);
}
MsnSlpLink
*
msn_slplink_ref
(
MsnSlpLink
*
slplink
)
{
g_return_val_if_fail
(
slplink
!=
NULL
,
NULL
);
slplink
->
refs
++
;
if
(
purple_debug_is_verbose
())
purple_debug_info
(
"msn"
,
"slplink ref (%p)[%d]
\n
"
,
slplink
,
slplink
->
refs
);
return
slplink
;
}
void
msn_slplink_unref
(
MsnSlpLink
*
slplink
)
{
g_return_if_fail
(
slplink
!=
NULL
);
slplink
->
refs
--
;
if
(
purple_debug_is_verbose
())
purple_debug_info
(
"msn"
,
"slplink unref (%p)[%d]
\n
"
,
slplink
,
slplink
->
refs
);
if
(
slplink
->
refs
==
0
)
msn_slplink_destroy
(
slplink
);
}
MsnSlpLink
*
msn_session_find_slplink
(
MsnSession
*
session
,
const
char
*
who
)
{
GList
*
l
;
for
(
l
=
session
->
slplinks
;
l
!=
NULL
;
l
=
l
->
next
)
{
MsnSlpLink
*
slplink
;
slplink
=
l
->
data
;
if
(
!
strcmp
(
slplink
->
remote_user
,
who
))
return
slplink
;
}
return
NULL
;
}
MsnSlpLink
*
msn_session_get_slplink
(
MsnSession
*
session
,
const
char
*
username
)
{
MsnSlpLink
*
slplink
;
g_return_val_if_fail
(
session
!=
NULL
,
NULL
);
g_return_val_if_fail
(
username
!=
NULL
,
NULL
);
slplink
=
msn_session_find_slplink
(
session
,
username
);
if
(
slplink
==
NULL
)
slplink
=
msn_slplink_new
(
session
,
username
);
return
slplink
;
}
void
msn_slplink_add_slpcall
(
MsnSlpLink
*
slplink
,
MsnSlpCall
*
slpcall
)
{
if
(
slplink
->
swboard
!=
NULL
)
slplink
->
swboard
->
flag
|=
MSN_SB_FLAG_FT
;
slplink
->
slp_calls
=
g_list_append
(
slplink
->
slp_calls
,
slpcall
);
/*
if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
msn_dc_ref(slplink->dc);
*/
}
void
msn_slplink_remove_slpcall
(
MsnSlpLink
*
slplink
,
MsnSlpCall
*
slpcall
)
{
/*
if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED)
msn_dc_unref(slplink->dc);
*/
slplink
->
slp_calls
=
g_list_remove
(
slplink
->
slp_calls
,
slpcall
);
/* The slplink has no slpcalls in it, release it from MSN_SB_FLAG_FT.
* If nothing else is using it then this might cause swboard to be
* destroyed. */
if
(
slplink
->
slp_calls
==
NULL
&&
slplink
->
swboard
!=
NULL
)
{
slplink
->
swboard
->
slplinks
=
g_list_remove
(
slplink
->
swboard
->
slplinks
,
slplink
);
msn_switchboard_release
(
slplink
->
swboard
,
MSN_SB_FLAG_FT
);
slplink
->
swboard
=
NULL
;
}
if
(
slplink
->
dc
!=
NULL
)
{
if
((
slplink
->
dc
->
state
!=
DC_STATE_ESTABLISHED
&&
slplink
->
dc
->
slpcall
==
slpcall
)
||
(
slplink
->
slp_calls
==
NULL
))
{
/* The DC is not established and its corresponding slpcall is dead,
* or the slplink has no slpcalls in it and no longer needs the DC.
*/
slplink
->
dc
->
slplink
=
NULL
;
msn_dc_destroy
(
slplink
->
dc
);
slplink
->
dc
=
NULL
;
}
}
}
MsnSlpCall
*
msn_slplink_find_slp_call
(
MsnSlpLink
*
slplink
,
const
char
*
id
)
{
GList
*
l
;
MsnSlpCall
*
slpcall
;
if
(
!
id
)
return
NULL
;
for
(
l
=
slplink
->
slp_calls
;
l
!=
NULL
;
l
=
l
->
next
)
{
slpcall
=
l
->
data
;
if
(
slpcall
->
id
&&
!
strcmp
(
slpcall
->
id
,
id
))
return
slpcall
;
}
return
NULL
;
}
MsnSlpCall
*
msn_slplink_find_slp_call_with_session_id
(
MsnSlpLink
*
slplink
,
long
id
)
{
GList
*
l
;
MsnSlpCall
*
slpcall
;
for
(
l
=
slplink
->
slp_calls
;
l
!=
NULL
;
l
=
l
->
next
)
{
slpcall
=
l
->
data
;
if
(
slpcall
->
session_id
==
id
)
return
slpcall
;
}
return
NULL
;
}
MsnP2PVersion
msn_slplink_get_p2p_version
(
MsnSlpLink
*
slplink
)
{
return
slplink
->
p2p_version
;
}
static
void
msn_slplink_send_part
(
MsnSlpLink
*
slplink
,
MsnSlpMessagePart
*
part
)
{
if
(
slplink
->
dc
!=
NULL
&&
slplink
->
dc
->
state
==
DC_STATE_ESTABLISHED
)
{
msn_dc_enqueue_part
(
slplink
->
dc
,
part
);
}
else
{
msn_sbconn_send_part
(
slplink
,
part
);
}
}
void
msn_slplink_send_msgpart
(
MsnSlpLink
*
slplink
,
MsnSlpMessage
*
slpmsg
)
{
MsnSlpMessagePart
*
part
;
MsnP2PInfo
*
info
;
long
long
real_size
;
size_t
len
=
0
;
guint64
offset
;
/* Maybe we will want to create a new msg for this slpmsg instead of
* reusing the same one all the time. */
info
=
slpmsg
->
p2p_info
;
part
=
msn_slpmsgpart_new
(
msn_p2p_info_dup
(
info
));
part
->
ack_data
=
slpmsg
;
real_size
=
msn_p2p_info_is_ack
(
info
)
?
0
:
slpmsg
->
size
;
offset
=
msn_p2p_info_get_offset
(
info
);
if
(
offset
<
real_size
)
{
if
(
slpmsg
->
slpcall
&&
slpmsg
->
slpcall
->
xfer
&&
purple_xfer_get_type
(
slpmsg
->
slpcall
->
xfer
)
==
PURPLE_XFER_SEND
&&
purple_xfer_get_status
(
slpmsg
->
slpcall
->
xfer
)
==
PURPLE_XFER_STATUS_STARTED
)
{
len
=
MIN
(
MSN_SBCONN_MAX_SIZE
,
slpmsg
->
slpcall
->
u
.
outgoing
.
len
);
msn_slpmsgpart_set_bin_data
(
part
,
slpmsg
->
slpcall
->
u
.
outgoing
.
data
,
len
);
}
else
{
len
=
slpmsg
->
size
-
offset
;
if
(
len
>
MSN_SBCONN_MAX_SIZE
)
len
=
MSN_SBCONN_MAX_SIZE
;
msn_slpmsgpart_set_bin_data
(
part
,
slpmsg
->
buffer
+
offset
,
len
);
}
msn_p2p_info_set_length
(
slpmsg
->
p2p_info
,
len
);
}
#if 0
/* TODO: port this function to SlpMessageParts */
if (purple_debug_is_verbose())
msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body);
#endif
#ifdef MSN_DEBUG_SLP_FILES
debug_part_to_file
(
slpmsg
,
TRUE
);
#endif
slpmsg
->
parts
=
g_list_append
(
slpmsg
->
parts
,
part
);
msn_slplink_send_part
(
slplink
,
part
);
if
(
msn_p2p_msg_is_data
(
info
)
&&
slpmsg
->
slpcall
!=
NULL
)
{
slpmsg
->
slpcall
->
progress
=
TRUE
;
if
(
slpmsg
->
slpcall
->
progress_cb
!=
NULL
)
{
slpmsg
->
slpcall
->
progress_cb
(
slpmsg
->
slpcall
,
slpmsg
->
size
,
len
);
}
}
/* slpmsg->offset += len; */
}
static
void
msn_slplink_release_slpmsg
(
MsnSlpLink
*
slplink
,
MsnSlpMessage
*
slpmsg
)
{
MsnP2PInfo
*
info
;
guint32
flags
;
info
=
slpmsg
->
p2p_info
;
flags
=
msn_p2p_info_get_flags
(
info
);
if
(
flags
==
P2P_NO_FLAG
)
{
msn_p2p_info_set_ack_id
(
info
,
rand
()
%
0xFFFFFF00
);
}
else
if
(
msn_p2p_msg_is_data
(
info
))
{
MsnSlpCall
*
slpcall
;
slpcall
=
slpmsg
->
slpcall
;
g_return_if_fail
(
slpcall
!=
NULL
);
msn_p2p_info_set_session_id
(
info
,
slpcall
->
session_id
);
msn_p2p_info_set_app_id
(
info
,
slpcall
->
app_id
);
msn_p2p_info_set_ack_id
(
info
,
rand
()
%
0xFFFFFF00
);
}
msn_p2p_info_set_id
(
info
,
slpmsg
->
id
);
msn_p2p_info_set_total_size
(
info
,
slpmsg
->
size
);
msn_slplink_send_msgpart
(
slplink
,
slpmsg
);
}
void
msn_slplink_queue_slpmsg
(
MsnSlpLink
*
slplink
,
MsnSlpMessage
*
slpmsg
)
{
g_return_if_fail
(
slpmsg
!=
NULL
);
slpmsg
->
id
=
slplink
->
slp_seq_id
++
;
g_queue_push_tail
(
slplink
->
slp_msg_queue
,
slpmsg
);
}
void
msn_slplink_send_slpmsg
(
MsnSlpLink
*
slplink
,
MsnSlpMessage
*
slpmsg
)
{
slpmsg
->
id
=
slplink
->
slp_seq_id
++
;
msn_slplink_release_slpmsg
(
slplink
,
slpmsg
);
}
void
msn_slplink_send_queued_slpmsgs
(
MsnSlpLink
*
slplink
)
{
MsnSlpMessage
*
slpmsg
;
/* Send the queued msgs in the order they were created */
while
((
slpmsg
=
g_queue_pop_head
(
slplink
->
slp_msg_queue
))
!=
NULL
)
{
msn_slplink_release_slpmsg
(
slplink
,
slpmsg
);
}
}
static
void
msn_slplink_send_ack
(
MsnSlpLink
*
slplink
,
MsnP2PInfo
*
info
)
{
MsnSlpMessage
*
slpmsg
=
msn_slpmsg_ack_new
(
slplink
,
info
);
msn_slplink_send_slpmsg
(
slplink
,
slpmsg
);
msn_slpmsg_destroy
(
slpmsg
);
}
static
MsnSlpMessage
*
msn_slplink_message_find
(
MsnSlpLink
*
slplink
,
long
session_id
,
long
id
)
{
GList
*
e
;
for
(
e
=
slplink
->
slp_msgs
;
e
!=
NULL
;
e
=
e
->
next
)
{
MsnSlpMessage
*
slpmsg
=
e
->
data
;
if
((
msn_p2p_info_get_session_id
(
slpmsg
->
p2p_info
)
==
session_id
)
&&
(
slpmsg
->
id
==
id
))
return
slpmsg
;
}
return
NULL
;
}
static
MsnSlpMessage
*
init_first_msg
(
MsnSlpLink
*
slplink
,
MsnP2PInfo
*
info
)
{
MsnSlpMessage
*
slpmsg
;
guint32
session_id
;
slpmsg
=
msn_slpmsg_new
(
slplink
,
NULL
);
slpmsg
->
id
=
msn_p2p_info_get_id
(
info
);
session_id
=
msn_p2p_info_get_session_id
(
info
);
slpmsg
->
size
=
msn_p2p_info_get_total_size
(
info
);
msn_p2p_info_init_first
(
slpmsg
->
p2p_info
,
info
);
if
(
session_id
)
{
slpmsg
->
slpcall
=
msn_slplink_find_slp_call_with_session_id
(
slplink
,
session_id
);
if
(
slpmsg
->
slpcall
!=
NULL
)
{
if
(
msn_p2p_msg_is_data
(
info
))
{
PurpleXfer
*
xfer
=
slpmsg
->
slpcall
->
xfer
;
if
(
xfer
!=
NULL
)
{
slpmsg
->
ft
=
TRUE
;
slpmsg
->
slpcall
->
xfer_msg
=
slpmsg
;
purple_xfer_ref
(
xfer
);
purple_xfer_start
(
xfer
,
-1
,
NULL
,
0
);
if
(
xfer
->
data
==
NULL
)
{
purple_xfer_unref
(
xfer
);
msn_slpmsg_destroy
(
slpmsg
);
g_return_val_if_reached
(
NULL
);
}
else
{
purple_xfer_unref
(
xfer
);
}
}
}
}
}
if
(
!
slpmsg
->
ft
&&
slpmsg
->
size
)
{
slpmsg
->
buffer
=
g_try_malloc
(
slpmsg
->
size
);
if
(
slpmsg
->
buffer
==
NULL
)
{
purple_debug_error
(
"msn"
,
"Failed to allocate buffer for slpmsg
\n
"
);
msn_slpmsg_destroy
(
slpmsg
);
return
NULL
;
}
}
return
slpmsg
;
}
static
void
process_complete_msg
(
MsnSlpLink
*
slplink
,
MsnSlpMessage
*
slpmsg
,
MsnP2PInfo
*
info
)
{
MsnSlpCall
*
slpcall
;
slpcall
=
msn_slp_process_msg
(
slplink
,
slpmsg
);
if
(
slpcall
==
NULL
)
{
msn_slpmsg_destroy
(
slpmsg
);
return
;
}
purple_debug_info
(
"msn"
,
"msn_slplink_process_msg: slpmsg complete
\n
"
);
if
(
msn_p2p_info_require_ack
(
slpmsg
->
p2p_info
))
{
/* Release all the messages and send the ACK */
if
(
slpcall
->
wait_for_socket
)
{
/*
* Save ack for later because we have to send
* a 200 OK message to the previous direct connect
* invitation before ACK but the listening socket isn't
* created yet.
*/
purple_debug_info
(
"msn"
,
"msn_slplink_process_msg: save ACK
\n
"
);
slpcall
->
slplink
->
dc
->
prev_ack
=
msn_slpmsg_ack_new
(
slplink
,
info
);
}
else
if
(
!
slpcall
->
wasted
)
{
purple_debug_info
(
"msn"
,
"msn_slplink_process_msg: send ACK
\n
"
);
msn_slplink_send_ack
(
slplink
,
info
);
msn_slplink_send_queued_slpmsgs
(
slplink
);
}
}
msn_slpmsg_destroy
(
slpmsg
);
if
(
!
slpcall
->
wait_for_socket
&&
slpcall
->
wasted
)
msn_slpcall_destroy
(
slpcall
);
}
static
void
slpmsg_add_part
(
MsnSlpMessage
*
slpmsg
,
MsnSlpMessagePart
*
part
)
{
if
(
slpmsg
->
ft
)
{
slpmsg
->
slpcall
->
u
.
incoming_data
=
g_byte_array_append
(
slpmsg
->
slpcall
->
u
.
incoming_data
,
(
const
guchar
*
)
part
->
buffer
,
part
->
size
);
purple_xfer_prpl_ready
(
slpmsg
->
slpcall
->
xfer
);
}
else
if
(
slpmsg
->
size
&&
slpmsg
->
buffer
)
{
guint64
offset
=
msn_p2p_info_get_offset
(
part
->
info
);
if
(
G_MAXSIZE
-
part
->
size
<
offset
||
(
offset
+
part
->
size
)
>
slpmsg
->
size
||
msn_p2p_info_get_offset
(
slpmsg
->
p2p_info
)
!=
offset
)
{
purple_debug_error
(
"msn"
,
"Oversized slpmsg - msgsize=%lld offset=%"
G_GUINT64_FORMAT
" len=%"
G_GSIZE_FORMAT
"
\n
"
,
slpmsg
->
size
,
offset
,
part
->
size
);
g_return_if_reached
();
}
else
{
memcpy
(
slpmsg
->
buffer
+
offset
,
part
->
buffer
,
part
->
size
);
msn_p2p_info_set_offset
(
slpmsg
->
p2p_info
,
offset
+
part
->
size
);
}
}
}
void
msn_slplink_process_msg
(
MsnSlpLink
*
slplink
,
MsnSlpMessagePart
*
part
)
{
MsnSlpMessage
*
slpmsg
;
MsnP2PInfo
*
info
;
info
=
part
->
info
;
if
(
!
msn_p2p_info_is_valid
(
info
))
{
/* We seem to have received a bad header */
purple_debug_warning
(
"msn"
,
"Total size listed in SLP binary header "
"was less than length of this particular message. This "
"should not happen. Dropping message.
\n
"
);
return
;
}
if
(
msn_p2p_info_is_first
(
info
))
slpmsg
=
init_first_msg
(
slplink
,
info
);
else
{
guint32
session_id
,
id
;
session_id
=
msn_p2p_info_get_session_id
(
info
);
id
=
msn_p2p_info_get_id
(
info
);
slpmsg
=
msn_slplink_message_find
(
slplink
,
session_id
,
id
);
if
(
slpmsg
==
NULL
)
{
/* Probably the transfer was cancelled */
purple_debug_error
(
"msn"
,
"Couldn't find slpmsg
\n
"
);
return
;
}
}
slpmsg_add_part
(
slpmsg
,
part
);
if
(
msn_p2p_msg_is_data
(
slpmsg
->
p2p_info
)
&&
slpmsg
->
slpcall
!=
NULL
)
{
slpmsg
->
slpcall
->
progress
=
TRUE
;
if
(
slpmsg
->
slpcall
->
progress_cb
!=
NULL
)
{
slpmsg
->
slpcall
->
progress_cb
(
slpmsg
->
slpcall
,
slpmsg
->
size
,
part
->
size
);
}
}
#if 0
if (slpmsg->buffer == NULL)
return;
#endif
/* All the pieces of the slpmsg have been received */
if
(
msn_p2p_info_is_final
(
info
))
process_complete_msg
(
slplink
,
slpmsg
,
info
);
/* NOTE: The slpmsg will be destroyed in process_complete_msg or left in
the slplink until fully received. Don't free it here!
*/
}
void
msn_slplink_request_object
(
MsnSlpLink
*
slplink
,
const
char
*
info
,
MsnSlpCb
cb
,
MsnSlpEndCb
end_cb
,
const
MsnObject
*
obj
)
{
MsnSlpCall
*
slpcall
;
char
*
msnobj_data
;
char
*
msnobj_base64
;
g_return_if_fail
(
slplink
!=
NULL
);
g_return_if_fail
(
obj
!=
NULL
);
msnobj_data
=
msn_object_to_string
(
obj
);
msnobj_base64
=
purple_base64_encode
((
const
guchar
*
)
msnobj_data
,
strlen
(
msnobj_data
));
g_free
(
msnobj_data
);
slpcall
=
msn_slpcall_new
(
slplink
);
msn_slpcall_init
(
slpcall
,
MSN_SLPCALL_ANY
);
slpcall
->
data_info
=
g_strdup
(
info
);
slpcall
->
cb
=
cb
;
slpcall
->
end_cb
=
end_cb
;
msn_slpcall_invite
(
slpcall
,
MSN_OBJ_GUID
,
P2P_APPID_OBJ
,
msnobj_base64
);
g_free
(
msnobj_base64
);
}