pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Merged pidgin/main into default
2017-03-11, Arkadiy Illarionov
c979362981fb
Merged pidgin/main into default
/*
* MXit Protocol libPurple Plugin
*
* -- MXit client protocol implementation --
*
* Pieter Loubser <libpurple@mxit.com>
*
* (C) Copyright 2009 MXit Lifestyle (Pty) Ltd.
* <http://www.mxitlifestyle.com>
*
* 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
"version.h"
#include
"client.h"
#include
"mxit.h"
#include
"roster.h"
#include
"chunk.h"
#include
"filexfer.h"
#include
"markup.h"
#include
"multimx.h"
#include
"splashscreen.h"
#include
"login.h"
#include
"formcmds.h"
#include
"http.h"
#include
"cipher.h"
#define MXIT_MS_OFFSET 3
/* configure the right record terminator char to use */
#define CP_REC_TERM ( ( session->http ) ? CP_HTTP_REC_TERM : CP_SOCK_REC_TERM )
/*------------------------------------------------------------------------
* return the current timestamp in milliseconds
*/
gint64
mxit_now_milli
(
void
)
{
GTimeVal
now
;
g_get_current_time
(
&
now
);
return
(
(
now
.
tv_sec
*
1000
)
+
(
now
.
tv_usec
/
1000
)
);
}
/*------------------------------------------------------------------------
* Display a notification popup message to the user.
*
* @param type The type of notification:
* - info: PURPLE_NOTIFY_MSG_INFO
* - warning: PURPLE_NOTIFY_MSG_WARNING
* - error: PURPLE_NOTIFY_MSG_ERROR
* @param heading Heading text
* @param message Message text
*/
void
mxit_popup
(
int
type
,
const
char
*
heading
,
const
char
*
message
)
{
/* (reference: "libpurple/notify.h") */
purple_notify_message
(
NULL
,
type
,
_
(
MXIT_POPUP_WIN_NAME
),
heading
,
message
,
NULL
,
NULL
,
NULL
);
}
/*------------------------------------------------------------------------
* For compatibility with legacy clients, all usernames are sent from MXit with a domain
* appended. For MXit contacts, this domain is set to "@m". This function strips
* those fake domains.
*
* @param username The username of the contact
*/
void
mxit_strip_domain
(
char
*
username
)
{
if
(
g_str_has_suffix
(
username
,
"@m"
)
)
username
[
strlen
(
username
)
-
2
]
=
'\0'
;
}
/*------------------------------------------------------------------------
* Dump a byte buffer to the console for debugging purposes.
*
* @param buf The data
* @param len The data length
*/
void
dump_bytes
(
struct
MXitSession
*
session
,
const
char
*
buf
,
int
len
)
{
char
*
msg
=
g_malloc0
(
len
+
1
);
int
i
;
for
(
i
=
0
;
i
<
len
;
i
++
)
{
char
ch
=
buf
[
i
];
if
(
ch
==
CP_REC_TERM
)
/* record terminator */
msg
[
i
]
=
'!'
;
else
if
(
ch
==
CP_FLD_TERM
)
/* field terminator */
msg
[
i
]
=
'^'
;
else
if
(
ch
==
CP_PKT_TERM
)
/* packet terminator */
msg
[
i
]
=
'@'
;
else
if
(
(
ch
<
0x20
)
||
(
ch
>
0x7E
)
)
/* non-printable character */
msg
[
i
]
=
'_'
;
else
msg
[
i
]
=
ch
;
}
purple_debug_info
(
MXIT_PLUGIN_ID
,
"DUMP: '%s'
\n
"
,
msg
);
g_free
(
msg
);
}
/*------------------------------------------------------------------------
* Determine if we have an active chat with a specific contact
*
* @param session The MXit session object
* @param who The contact name
* @return Return true if we have an active chat with the contact
*/
gboolean
find_active_chat
(
const
GList
*
chats
,
const
char
*
who
)
{
const
GList
*
list
=
chats
;
const
char
*
chat
=
NULL
;
while
(
list
)
{
chat
=
(
const
char
*
)
list
->
data
;
if
(
strcmp
(
chat
,
who
)
==
0
)
return
TRUE
;
list
=
g_list_next
(
list
);
}
return
FALSE
;
}
/*------------------------------------------------------------------------
* scnprintf
*
* @param string The destination buffer.
* @param size The maximum size of the destination buffer.
* @param format The format string
* @param ... The parameters to the format string.
* @return The number of characters actually stored in the buffer.
*/
static
int
scnprintf
(
gchar
*
string
,
size_t
size
,
const
char
*
format
,
...
)
{
va_list
args
;
guint
i
;
va_start
(
args
,
format
);
i
=
g_vsnprintf
(
string
,
size
,
format
,
args
);
va_end
(
args
);
if
(
i
<
size
)
return
i
;
else
if
(
size
>
0
)
/* destination buffer too short - return number of characters actually inserted */
return
size
-
1
;
else
return
0
;
}
/*========================================================================================================================
* Low-level Packet transmission
*/
/*------------------------------------------------------------------------
* Remove next packet from transmission queue.
*
* @param session The MXit session object
* @return The next packet for transmission (or NULL)
*/
static
struct
tx_packet
*
pop_tx_packet
(
struct
MXitSession
*
session
)
{
struct
tx_packet
*
packet
=
NULL
;
if
(
session
->
queue
.
count
>
0
)
{
/* dequeue the next packet */
packet
=
session
->
queue
.
packets
[
session
->
queue
.
rd_i
];
session
->
queue
.
packets
[
session
->
queue
.
rd_i
]
=
NULL
;
session
->
queue
.
rd_i
=
(
session
->
queue
.
rd_i
+
1
)
%
MAX_QUEUE_SIZE
;
session
->
queue
.
count
--
;
}
return
packet
;
}
/*------------------------------------------------------------------------
* Add packet to transmission queue.
*
* @param session The MXit session object
* @param packet The packet to transmit
* @return Return TRUE if packet was enqueue, or FALSE if queue is full.
*/
static
gboolean
push_tx_packet
(
struct
MXitSession
*
session
,
struct
tx_packet
*
packet
)
{
if
(
session
->
queue
.
count
<
MAX_QUEUE_SIZE
)
{
/* enqueue packet */
session
->
queue
.
packets
[
session
->
queue
.
wr_i
]
=
packet
;
session
->
queue
.
wr_i
=
(
session
->
queue
.
wr_i
+
1
)
%
MAX_QUEUE_SIZE
;
session
->
queue
.
count
++
;
return
TRUE
;
}
else
return
FALSE
;
/* queue is full */
}
/*------------------------------------------------------------------------
* Deallocate transmission packet.
*
* @param packet The packet to deallocate.
*/
static
void
free_tx_packet
(
struct
tx_packet
*
packet
)
{
g_free
(
packet
->
data
);
g_free
(
packet
);
packet
=
NULL
;
}
/*------------------------------------------------------------------------
* Flush all the packets from the tx queue and release the resources.
*
* @param session The MXit session object
*/
static
void
flush_queue
(
struct
MXitSession
*
session
)
{
struct
tx_packet
*
packet
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"flushing the tx queue
\n
"
);
while
(
(
packet
=
pop_tx_packet
(
session
)
)
!=
NULL
)
free_tx_packet
(
packet
);
}
/*------------------------------------------------------------------------
* TX Step 3: Write the packet data to the TCP connection.
*
* @param fd The file descriptor
* @param pktdata The packet data
* @param pktlen The length of the packet data
* @return Return -1 on error, otherwise 0
*/
static
int
mxit_write_sock_packet
(
int
fd
,
const
char
*
pktdata
,
int
pktlen
)
{
int
written
;
int
res
;
written
=
0
;
while
(
written
<
pktlen
)
{
res
=
write
(
fd
,
&
pktdata
[
written
],
pktlen
-
written
);
if
(
res
<=
0
)
{
/* error on socket */
if
(
errno
==
EAGAIN
)
continue
;
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Error while writing packet to MXit server (%i)
\n
"
,
res
);
return
-1
;
}
written
+=
res
;
}
return
0
;
}
/**
* Callback called for handling a HTTP GET response
*
* @param http_conn http api object (see http.h)
* @param response http api object (see http.h)
* @param _session The MXit session object
*/
static
void
mxit_cb_http_rx
(
PurpleHttpConnection
*
http_conn
,
PurpleHttpResponse
*
response
,
gpointer
_session
)
{
struct
MXitSession
*
session
=
_session
;
const
gchar
*
got_data
;
size_t
got_len
;
if
(
!
purple_http_response_is_successful
(
response
))
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"HTTP response error (%s)
\n
"
,
purple_http_response_get_error
(
response
));
return
;
}
/* convert the HTTP result */
got_data
=
purple_http_response_get_data
(
response
,
&
got_len
);
memcpy
(
session
->
rx_dbuf
,
got_data
,
got_len
);
session
->
rx_i
=
got_len
;
mxit_parse_packet
(
session
);
}
/**
* TX Step 3: Write the packet data to the HTTP connection (GET style).
*
* @param session The MXit session object
* @param packet The packet data
*/
static
void
mxit_write_http_get
(
struct
MXitSession
*
session
,
struct
tx_packet
*
packet
)
{
PurpleHttpRequest
*
req
;
char
*
part
=
NULL
;
if
(
packet
->
datalen
>
0
)
{
char
*
tmp
;
tmp
=
g_strndup
(
packet
->
data
,
packet
->
datalen
);
part
=
g_strdup
(
purple_url_encode
(
tmp
));
g_free
(
tmp
);
}
req
=
purple_http_request_new
(
NULL
);
purple_http_request_set_url_printf
(
req
,
"%s?%s%s"
,
session
->
http_server
,
purple_url_encode
(
packet
->
header
),
part
?
part
:
""
);
purple_http_request_header_set
(
req
,
"User-Agent"
,
MXIT_HTTP_USERAGENT
);
purple_http_connection_set_add
(
session
->
async_http_reqs
,
purple_http_request
(
session
->
con
,
req
,
mxit_cb_http_rx
,
session
));
purple_http_request_unref
(
req
);
g_free
(
part
);
}
/**
* TX Step 3: Write the packet data to the HTTP connection (POST style).
*
* @param session The MXit session object
* @param packet The packet data
*/
static
void
mxit_write_http_post
(
struct
MXitSession
*
session
,
struct
tx_packet
*
packet
)
{
PurpleHttpRequest
*
req
;
/* strip off the last '&' from the header */
packet
->
header
[
packet
->
headerlen
-
1
]
=
'\0'
;
packet
->
headerlen
--
;
req
=
purple_http_request_new
(
NULL
);
purple_http_request_set_url_printf
(
req
,
"%s?%s"
,
session
->
http_server
,
purple_url_encode
(
packet
->
header
));
purple_http_request_set_method
(
req
,
"POST"
);
purple_http_request_header_set
(
req
,
"User-Agent"
,
MXIT_HTTP_USERAGENT
);
purple_http_request_header_set
(
req
,
"Content-Type"
,
"application/octet-stream"
);
purple_http_request_set_contents
(
req
,
packet
->
data
+
MXIT_MS_OFFSET
,
packet
->
datalen
-
MXIT_MS_OFFSET
);
purple_http_connection_set_add
(
session
->
async_http_reqs
,
purple_http_request
(
session
->
con
,
req
,
mxit_cb_http_rx
,
session
));
purple_http_request_unref
(
req
);
}
/*------------------------------------------------------------------------
* TX Step 2: Handle the transmission of the packet to the MXit server.
*
* @param session The MXit session object
* @param packet The packet to transmit
*/
static
void
mxit_send_packet
(
struct
MXitSession
*
session
,
struct
tx_packet
*
packet
)
{
int
res
;
if
(
!
(
session
->
flags
&
MXIT_FLAG_CONNECTED
)
)
{
/* we are not connected so ignore all packets to be send */
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Dropping TX packet (we are not connected)
\n
"
);
return
;
}
purple_debug_info
(
MXIT_PLUGIN_ID
,
"Packet send CMD:%i (%i)
\n
"
,
packet
->
cmd
,
packet
->
headerlen
+
packet
->
datalen
);
#ifdef DEBUG_PROTOCOL
dump_bytes
(
session
,
packet
->
header
,
packet
->
headerlen
);
dump_bytes
(
session
,
packet
->
data
,
packet
->
datalen
);
#endif
if
(
!
session
->
http
)
{
/* socket connection */
char
data
[
packet
->
datalen
+
packet
->
headerlen
];
int
datalen
;
/* create raw data buffer */
memcpy
(
data
,
packet
->
header
,
packet
->
headerlen
);
memcpy
(
data
+
packet
->
headerlen
,
packet
->
data
,
packet
->
datalen
);
datalen
=
packet
->
headerlen
+
packet
->
datalen
;
res
=
mxit_write_sock_packet
(
session
->
fd
,
data
,
datalen
);
if
(
res
<
0
)
{
/* we must have lost the connection, so terminate it so that we can reconnect */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"We have lost the connection to MXit. Please reconnect."
)
);
}
}
else
{
/* http connection */
if
(
packet
->
cmd
==
CP_CMD_MEDIA
)
{
/* multimedia packets must be send with a HTTP POST */
mxit_write_http_post
(
session
,
packet
);
}
else
{
mxit_write_http_get
(
session
,
packet
);
}
}
/* update the timestamp of the last-transmitted packet */
session
->
last_tx
=
mxit_now_milli
();
/*
* we need to remember that we are still waiting for the ACK from
* the server on this request
*/
session
->
outack
=
packet
->
cmd
;
/* free up the packet resources */
free_tx_packet
(
packet
);
}
/*------------------------------------------------------------------------
* TX Step 1: Create a new Tx packet and queue it for sending.
*
* @param session The MXit session object
* @param data The packet data (payload)
* @param datalen The length of the packet data
* @param cmd The MXit command for this packet
*/
static
void
mxit_queue_packet
(
struct
MXitSession
*
session
,
const
char
*
data
,
int
datalen
,
int
cmd
)
{
struct
tx_packet
*
packet
;
char
header
[
256
];
int
hlen
;
/* create a packet for sending */
packet
=
g_new0
(
struct
tx_packet
,
1
);
packet
->
data
=
g_malloc0
(
datalen
);
packet
->
cmd
=
cmd
;
packet
->
headerlen
=
0
;
/* create generic packet header */
hlen
=
scnprintf
(
header
,
sizeof
(
header
),
"id=%s%c"
,
purple_account_get_username
(
session
->
acc
),
CP_REC_TERM
);
/* client mxitid */
if
(
session
->
http
)
{
/* http connection only */
hlen
+=
scnprintf
(
header
+
hlen
,
sizeof
(
header
)
-
hlen
,
"s="
);
if
(
session
->
http_sesid
>
0
)
{
hlen
+=
scnprintf
(
header
+
hlen
,
sizeof
(
header
)
-
hlen
,
"%u%c"
,
session
->
http_sesid
,
CP_FLD_TERM
);
/* http session id */
}
session
->
http_seqno
++
;
hlen
+=
scnprintf
(
header
+
hlen
,
sizeof
(
header
)
-
hlen
,
"%u%c"
,
session
->
http_seqno
,
CP_REC_TERM
);
/* http request sequence id */
}
hlen
+=
scnprintf
(
header
+
hlen
,
sizeof
(
header
)
-
hlen
,
"cm=%i%c"
,
cmd
,
CP_REC_TERM
);
/* packet command */
if
(
!
session
->
http
)
{
/* socket connection only */
packet
->
headerlen
=
scnprintf
(
packet
->
header
,
sizeof
(
packet
->
header
),
"ln=%i%c"
,
(
datalen
+
hlen
),
CP_REC_TERM
);
/* packet length */
}
/* copy the header to packet */
memcpy
(
packet
->
header
+
packet
->
headerlen
,
header
,
hlen
);
packet
->
headerlen
+=
hlen
;
/* copy payload to packet */
if
(
datalen
>
0
)
memcpy
(
packet
->
data
,
data
,
datalen
);
packet
->
datalen
=
datalen
;
/* shortcut */
if
(
(
session
->
queue
.
count
==
0
)
&&
(
session
->
outack
==
0
)
)
{
/* the queue is empty and there are no outstanding acks so we can write it directly */
mxit_send_packet
(
session
,
packet
);
}
else
{
/* we need to queue this packet */
if
(
(
packet
->
cmd
==
CP_CMD_PING
)
||
(
packet
->
cmd
==
CP_CMD_POLL
)
)
{
/* we do NOT queue HTTP poll nor socket ping packets */
free_tx_packet
(
packet
);
return
;
}
purple_debug_info
(
MXIT_PLUGIN_ID
,
"queueing packet for later sending cmd=%i
\n
"
,
cmd
);
if
(
!
push_tx_packet
(
session
,
packet
)
)
{
/* packet could not be queued for transmission */
mxit_popup
(
PURPLE_NOTIFY_MSG_ERROR
,
_
(
"Message Send Error"
),
_
(
"Unable to process your request at this time"
)
);
free_tx_packet
(
packet
);
}
}
}
/*------------------------------------------------------------------------
* Manage the packet send queue (send next packet, timeout's, etc).
*
* @param session The MXit session object
*/
static
void
mxit_manage_queue
(
struct
MXitSession
*
session
)
{
struct
tx_packet
*
packet
=
NULL
;
gint64
now
=
mxit_now_milli
();
if
(
!
(
session
->
flags
&
MXIT_FLAG_CONNECTED
)
)
{
/* we are not connected, so ignore the queue */
return
;
}
else
if
(
session
->
outack
>
0
)
{
/* we are still waiting for an outstanding ACK from the MXit server */
if
(
session
->
last_tx
<=
mxit_now_milli
()
-
(
MXIT_ACK_TIMEOUT
*
1000
)
)
{
/* ack timeout! so we close the connection here */
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_manage_queue: Timeout awaiting ACK for command '%i'
\n
"
,
session
->
outack
);
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Timeout while waiting for a response from the MXit server."
)
);
}
return
;
}
/*
* the mxit server has flood detection and it prevents you from sending messages to fast.
* this is a self defense mechanism, a very annoying feature. so the client must ensure that
* it does not send messages too fast otherwise mxit will ignore the user for 30 seconds.
* this is what we are trying to avoid here..
*/
if
(
session
->
q_fast_timer_id
==
0
)
{
/* the fast timer has not been set yet */
if
(
session
->
last_tx
>
(
now
-
MXIT_TX_DELAY
)
)
{
/* we need to wait a little before sending the next packet, so schedule a wakeup call */
gint64
tdiff
=
now
-
(
session
->
last_tx
);
guint
delay
=
(
MXIT_TX_DELAY
-
tdiff
)
+
9
;
if
(
delay
<=
0
)
delay
=
MXIT_TX_DELAY
;
session
->
q_fast_timer_id
=
purple_timeout_add
(
delay
,
mxit_manage_queue_fast
,
session
);
}
else
{
/* get the next packet from the queue to send */
packet
=
pop_tx_packet
(
session
);
if
(
packet
!=
NULL
)
{
/* there was a packet waiting to be sent to the server, now is the time to do something about it */
/* send the packet to MXit server */
mxit_send_packet
(
session
,
packet
);
}
}
}
}
/*------------------------------------------------------------------------
* Slow callback to manage the packet send queue.
*
* @param session The MXit session object
*/
gboolean
mxit_manage_queue_slow
(
gpointer
user_data
)
{
struct
MXitSession
*
session
=
(
struct
MXitSession
*
)
user_data
;
mxit_manage_queue
(
session
);
/* continue running */
return
TRUE
;
}
/*------------------------------------------------------------------------
* Fast callback to manage the packet send queue.
*
* @param session The MXit session object
*/
gboolean
mxit_manage_queue_fast
(
gpointer
user_data
)
{
struct
MXitSession
*
session
=
(
struct
MXitSession
*
)
user_data
;
session
->
q_fast_timer_id
=
0
;
mxit_manage_queue
(
session
);
/* stop running */
return
FALSE
;
}
/*------------------------------------------------------------------------
* Callback to manage HTTP server polling (HTTP connections ONLY)
*
* @param session The MXit session object
*/
gboolean
mxit_manage_polling
(
gpointer
user_data
)
{
struct
MXitSession
*
session
=
(
struct
MXitSession
*
)
user_data
;
gboolean
poll
=
FALSE
;
gint64
now
=
mxit_now_milli
();
gint64
rxdiff
;
if
(
!
(
session
->
flags
&
MXIT_FLAG_LOGGEDIN
)
)
{
/* we only poll if we are actually logged in */
return
TRUE
;
}
/* calculate the time differences */
rxdiff
=
now
-
session
->
last_rx
;
if
(
rxdiff
<
MXIT_HTTP_POLL_MIN
)
{
/* we received some reply a few moments ago, so reset the poll interval */
session
->
http_interval
=
MXIT_HTTP_POLL_MIN
;
}
else
if
(
session
->
http_last_poll
<
(
now
-
session
->
http_interval
)
)
{
/* time to poll again */
poll
=
TRUE
;
/* back-off some more with the polling */
session
->
http_interval
=
session
->
http_interval
+
(
session
->
http_interval
/
2
);
if
(
session
->
http_interval
>
MXIT_HTTP_POLL_MAX
)
session
->
http_interval
=
MXIT_HTTP_POLL_MAX
;
}
/* debugging */
//purple_debug_info( MXIT_PLUGIN_ID, "POLL TIMER: %i (%i)\n", session->http_interval, rxdiff );
if
(
poll
)
{
/* send poll request */
session
->
http_last_poll
=
mxit_now_milli
();
mxit_send_poll
(
session
);
}
return
TRUE
;
}
/*========================================================================================================================
* Send MXit operations.
*/
/*------------------------------------------------------------------------
* Send a ping/keepalive packet to MXit server.
*
* @param session The MXit session object
*/
void
mxit_send_ping
(
struct
MXitSession
*
session
)
{
/* queue packet for transmission */
mxit_queue_packet
(
session
,
NULL
,
0
,
CP_CMD_PING
);
}
/*------------------------------------------------------------------------
* Send a poll request to the HTTP server (HTTP connections ONLY).
*
* @param session The MXit session object
*/
void
mxit_send_poll
(
struct
MXitSession
*
session
)
{
/* queue packet for transmission */
mxit_queue_packet
(
session
,
NULL
,
0
,
CP_CMD_POLL
);
}
/*------------------------------------------------------------------------
* Send a logout packet to the MXit server.
*
* @param session The MXit session object
*/
void
mxit_send_logout
(
struct
MXitSession
*
session
)
{
/* queue packet for transmission */
mxit_queue_packet
(
session
,
NULL
,
0
,
CP_CMD_LOGOUT
);
}
/*------------------------------------------------------------------------
* Send a register packet to the MXit server.
*
* @param session The MXit session object
*/
void
mxit_send_register
(
struct
MXitSession
*
session
)
{
struct
MXitProfile
*
profile
=
session
->
profile
;
const
char
*
locale
;
char
data
[
CP_MAX_PACKET
];
int
datalen
;
char
*
clientVersion
;
unsigned
int
features
=
MXIT_CP_FEATURES
;
locale
=
purple_account_get_string
(
session
->
acc
,
MXIT_CONFIG_LOCALE
,
MXIT_DEFAULT_LOCALE
);
/* generate client version string (eg, P-2.7.10-Y-PURPLE) */
clientVersion
=
g_strdup_printf
(
"%c-%i.%i.%i-%s-%s"
,
MXIT_CP_DISTCODE
,
PURPLE_MAJOR_VERSION
,
PURPLE_MINOR_VERSION
,
PURPLE_MICRO_VERSION
,
MXIT_CP_ARCH
,
MXIT_CP_PLATFORM
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%i%c%s%c"
/* "ms"=password\1version\1maxreplyLen\1name\1 */
"%s%c%i%c%s%c%s%c"
/* dateOfBirth\1gender\1location\1capabilities\1 */
"%s%c%i%c%s%c%s"
/* dc\1features\1dialingcode\1locale */
"%c%i%c%i"
,
/* \1protocolVer\1lastRosterUpdate */
session
->
encpwd
,
CP_FLD_TERM
,
clientVersion
,
CP_FLD_TERM
,
CP_MAX_FILESIZE
,
CP_FLD_TERM
,
profile
->
nickname
,
CP_FLD_TERM
,
profile
->
birthday
,
CP_FLD_TERM
,
(
profile
->
male
)
?
1
:
0
,
CP_FLD_TERM
,
MXIT_DEFAULT_LOC
,
CP_FLD_TERM
,
MXIT_CP_CAP
,
CP_FLD_TERM
,
session
->
distcode
,
CP_FLD_TERM
,
features
,
CP_FLD_TERM
,
session
->
dialcode
,
CP_FLD_TERM
,
locale
,
CP_FLD_TERM
,
MXIT_CP_PROTO_VESION
,
CP_FLD_TERM
,
0
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_REGISTER
);
g_free
(
clientVersion
);
}
/*------------------------------------------------------------------------
* Send a login packet to the MXit server.
*
* @param session The MXit session object
*/
void
mxit_send_login
(
struct
MXitSession
*
session
)
{
const
char
*
splashId
;
const
char
*
locale
;
char
data
[
CP_MAX_PACKET
];
int
datalen
;
char
*
clientVersion
;
unsigned
int
features
=
MXIT_CP_FEATURES
;
locale
=
purple_account_get_string
(
session
->
acc
,
MXIT_CONFIG_LOCALE
,
MXIT_DEFAULT_LOCALE
);
/* generate client version string (eg, P-2.7.10-Y-PURPLE) */
clientVersion
=
g_strdup_printf
(
"%c-%i.%i.%i-%s-%s"
,
MXIT_CP_DISTCODE
,
PURPLE_MAJOR_VERSION
,
PURPLE_MINOR_VERSION
,
PURPLE_MICRO_VERSION
,
MXIT_CP_ARCH
,
MXIT_CP_PLATFORM
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%i%c"
/* "ms"=password\1version\1getContacts\1 */
"%s%c%s%c%i%c"
/* capabilities\1dc\1features\1 */
"%s%c%s%c"
/* dialingcode\1locale\1 */
"%i%c%i%c%i"
,
/* maxReplyLen\1protocolVer\1lastRosterUpdate */
session
->
encpwd
,
CP_FLD_TERM
,
clientVersion
,
CP_FLD_TERM
,
1
,
CP_FLD_TERM
,
MXIT_CP_CAP
,
CP_FLD_TERM
,
session
->
distcode
,
CP_FLD_TERM
,
features
,
CP_FLD_TERM
,
session
->
dialcode
,
CP_FLD_TERM
,
locale
,
CP_FLD_TERM
,
CP_MAX_FILESIZE
,
CP_FLD_TERM
,
MXIT_CP_PROTO_VESION
,
CP_FLD_TERM
,
0
);
/* include "custom resource" information */
splashId
=
splash_current
(
session
);
if
(
splashId
!=
NULL
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%ccr=%s"
,
CP_REC_TERM
,
splashId
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_LOGIN
);
g_free
(
clientVersion
);
}
/*------------------------------------------------------------------------
* Send a chat message packet to the MXit server.
*
* @param session The MXit session object
* @param to The username of the recipient
* @param msg The message text
*/
void
mxit_send_message
(
struct
MXitSession
*
session
,
const
char
*
to
,
const
char
*
msg
,
gboolean
parse_markup
,
gboolean
is_command
)
{
char
data
[
CP_MAX_PACKET
];
char
*
markuped_msg
;
int
datalen
;
int
msgtype
=
(
is_command
?
CP_MSGTYPE_COMMAND
:
CP_MSGTYPE_NORMAL
);
/* first we need to convert the markup from libPurple to MXit format */
if
(
parse_markup
)
markuped_msg
=
mxit_convert_markup_tx
(
msg
,
&
msgtype
);
else
markuped_msg
=
g_strdup
(
msg
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%i%c%i"
,
/* "ms"=jid\1msg\1type\1flags */
to
,
CP_FLD_TERM
,
markuped_msg
,
CP_FLD_TERM
,
msgtype
,
CP_FLD_TERM
,
CP_MSG_MARKUP
|
CP_MSG_EMOTICON
);
/* free the resources */
g_free
(
markuped_msg
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_TX_MSG
);
}
/*------------------------------------------------------------------------
* Send a extended profile request packet to the MXit server.
*
* @param session The MXit session object
* @param username Username who's profile is being requested (NULL = our own)
* @param nr_attribs Number of attributes being requested
* @param attribute The names of the attributes
*/
void
mxit_send_extprofile_request
(
struct
MXitSession
*
session
,
const
char
*
username
,
unsigned
int
nr_attrib
,
const
char
*
attribute
[]
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
unsigned
int
i
;
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%i"
,
/* "ms="mxitid\1nr_attributes */
(
username
?
username
:
""
),
CP_FLD_TERM
,
nr_attrib
);
/* add attributes */
for
(
i
=
0
;
i
<
nr_attrib
;
i
++
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
attribute
[
i
]
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_EXTPROFILE_GET
);
}
/*------------------------------------------------------------------------
* Send an update profile packet to the MXit server.
*
* @param session The MXit session object
* @param password The new password to be used for logging in (optional)
* @param nr_attrib The number of attributes
* @param attributes String containing the attribute-name, attribute-type and value (seperated by '\01')
*/
void
mxit_send_extprofile_update
(
struct
MXitSession
*
session
,
const
char
*
password
,
unsigned
int
nr_attrib
,
const
char
*
attributes
)
{
char
data
[
CP_MAX_PACKET
];
gchar
**
parts
=
NULL
;
int
datalen
;
unsigned
int
i
;
if
(
attributes
)
parts
=
g_strsplit
(
attributes
,
"
\01
"
,
1
+
(
nr_attrib
*
3
)
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%i"
,
/* "ms"=password\1nr_attibutes */
(
password
)
?
password
:
""
,
CP_FLD_TERM
,
nr_attrib
);
/* add attributes */
for
(
i
=
1
;
i
<
nr_attrib
*
3
;
i
+=
3
)
{
if
(
parts
==
NULL
||
parts
[
i
]
==
NULL
||
parts
[
i
+
1
]
==
NULL
||
parts
[
i
+
2
]
==
NULL
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Invalid profile update attributes = '%s' - nbr=%u
\n
"
,
attributes
,
nr_attrib
);
g_strfreev
(
parts
);
return
;
}
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s%c%s%c%s"
,
/* \1name\1type\1value */
CP_FLD_TERM
,
parts
[
i
],
CP_FLD_TERM
,
parts
[
i
+
1
],
CP_FLD_TERM
,
parts
[
i
+
2
]
);
}
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_EXTPROFILE_SET
);
/* freeup the memory */
g_strfreev
(
parts
);
}
/*------------------------------------------------------------------------
* Send packet to request list of suggested friends.
*
* @param session The MXit session object
* @param max Maximum number of results to return
* @param nr_attribs Number of attributes being requested
* @param attribute The names of the attributes
*/
void
mxit_send_suggest_friends
(
struct
MXitSession
*
session
,
int
max
,
unsigned
int
nr_attrib
,
const
char
*
attribute
[]
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
unsigned
int
i
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%i%c%s%c%i%c%i%c%i"
,
/* inputType \1 input \1 maxSuggestions \1 startIndex \1 numAttributes \1 name0 \1 name1 ... \1 nameN */
CP_SUGGEST_FRIENDS
,
CP_FLD_TERM
,
""
,
CP_FLD_TERM
,
max
,
CP_FLD_TERM
,
0
,
CP_FLD_TERM
,
nr_attrib
);
/* add attributes */
for
(
i
=
0
;
i
<
nr_attrib
;
i
++
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
attribute
[
i
]
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_SUGGESTCONTACTS
);
}
/*------------------------------------------------------------------------
* Send packet to perform a search for users.
*
* @param session The MXit session object
* @param max Maximum number of results to return
* @param text The search text
* @param nr_attribs Number of attributes being requested
* @param attribute The names of the attributes
*/
void
mxit_send_suggest_search
(
struct
MXitSession
*
session
,
int
max
,
const
char
*
text
,
unsigned
int
nr_attrib
,
const
char
*
attribute
[]
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
unsigned
int
i
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%i%c%s%c%i%c%i%c%i"
,
/* inputType \1 input \1 maxSuggestions \1 startIndex \1 numAttributes \1 name0 \1 name1 ... \1 nameN */
CP_SUGGEST_SEARCH
,
CP_FLD_TERM
,
text
,
CP_FLD_TERM
,
max
,
CP_FLD_TERM
,
0
,
CP_FLD_TERM
,
nr_attrib
);
/* add attributes */
for
(
i
=
0
;
i
<
nr_attrib
;
i
++
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
attribute
[
i
]
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_SUGGESTCONTACTS
);
}
/*------------------------------------------------------------------------
* Send a presence update packet to the MXit server.
*
* @param session The MXit session object
* @param presence The presence (as per MXit types)
* @param statusmsg The status message (can be NULL)
*/
void
mxit_send_presence
(
struct
MXitSession
*
session
,
int
presence
,
const
char
*
statusmsg
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%i%c"
,
/* "ms"=show\1status */
presence
,
CP_FLD_TERM
);
/* append status message (if one is set) */
if
(
statusmsg
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%s"
,
statusmsg
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_STATUS
);
}
/*------------------------------------------------------------------------
* Send a mood update packet to the MXit server.
*
* @param session The MXit session object
* @param mood The mood (as per MXit types)
*/
void
mxit_send_mood
(
struct
MXitSession
*
session
,
int
mood
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%i"
,
/* "ms"=mood */
mood
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MOOD
);
}
/*------------------------------------------------------------------------
* Send an invite contact packet to the MXit server.
*
* @param session The MXit session object
* @param username The username of the contact being invited
* @param mxitid Indicates the username is a MXitId.
* @param alias Our alias for the contact
* @param groupname Group in which contact should be stored.
* @param message Invite message
*/
void
mxit_send_invite
(
struct
MXitSession
*
session
,
const
char
*
username
,
gboolean
mxitid
,
const
char
*
alias
,
const
char
*
groupname
,
const
char
*
message
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%s%c%i%c%s%c%i"
,
/* "ms"=group \1 username \1 alias \1 type \1 msg \1 isuserid */
groupname
,
CP_FLD_TERM
,
username
,
CP_FLD_TERM
,
alias
,
CP_FLD_TERM
,
MXIT_TYPE_MXIT
,
CP_FLD_TERM
,
(
message
?
message
:
""
),
CP_FLD_TERM
,
(
mxitid
?
0
:
1
)
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_INVITE
);
}
/*------------------------------------------------------------------------
* Send a remove contact packet to the MXit server.
*
* @param session The MXit session object
* @param username The username of the contact being removed
*/
void
mxit_send_remove
(
struct
MXitSession
*
session
,
const
char
*
username
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s"
,
/* "ms"=username */
username
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_REMOVE
);
}
/*------------------------------------------------------------------------
* Send an accept subscription (invite) packet to the MXit server.
*
* @param session The MXit session object
* @param username The username of the contact being accepted
* @param alias Our alias for the contact
*/
void
mxit_send_allow_sub
(
struct
MXitSession
*
session
,
const
char
*
username
,
const
char
*
alias
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%s"
,
/* "ms"=username\1group\1alias */
username
,
CP_FLD_TERM
,
""
,
CP_FLD_TERM
,
alias
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_ALLOW
);
}
/*------------------------------------------------------------------------
* Send an deny subscription (invite) packet to the MXit server.
*
* @param session The MXit session object
* @param username The username of the contact being denied
* @param reason The message describing the reason for the rejection (can be NULL).
*/
void
mxit_send_deny_sub
(
struct
MXitSession
*
session
,
const
char
*
username
,
const
char
*
reason
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s"
,
/* "ms"=username */
username
);
/* append reason (if one is set) */
if
(
reason
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
reason
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_DENY
);
}
/*------------------------------------------------------------------------
* Send an update contact packet to the MXit server.
*
* @param session The MXit session object
* @param username The username of the contact being denied
* @param alias Our alias for the contact
* @param groupname Group in which contact should be stored.
*/
void
mxit_send_update_contact
(
struct
MXitSession
*
session
,
const
char
*
username
,
const
char
*
alias
,
const
char
*
groupname
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%s"
,
/* "ms"=groupname\1username\1alias */
groupname
,
CP_FLD_TERM
,
username
,
CP_FLD_TERM
,
alias
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_UPDATE
);
}
/*------------------------------------------------------------------------
* Send a splash-screen click event packet.
*
* @param session The MXit session object
* @param splashid The identifier of the splash-screen
*/
void
mxit_send_splashclick
(
struct
MXitSession
*
session
,
const
char
*
splashid
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s"
,
/* "ms"=splashId */
splashid
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_SPLASHCLICK
);
}
/*------------------------------------------------------------------------
* Send a message event packet.
*
* @param session The MXit session object
* @param to The username of the original sender (ie, recipient of the event)
* @param id The identifier of the event (received in message)
* @param event Identified the type of event
*/
void
mxit_send_msgevent
(
struct
MXitSession
*
session
,
const
char
*
to
,
const
char
*
id
,
int
event
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_send_msgevent: to=%s id=%s event=%i
\n
"
,
to
,
id
,
event
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%s%c%i"
,
/* "ms"=contactAddress \1 id \1 event */
to
,
CP_FLD_TERM
,
id
,
CP_FLD_TERM
,
event
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MSGEVENT
);
}
/*------------------------------------------------------------------------
* Send packet to create a MultiMX room.
*
* @param session The MXit session object
* @param groupname Name of the room to create
* @param nr_usernames Number of users in initial invite
* @param usernames The usernames of the users in the initial invite
*/
void
mxit_send_groupchat_create
(
struct
MXitSession
*
session
,
const
char
*
groupname
,
int
nr_usernames
,
const
char
*
usernames
[]
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
int
i
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%i"
,
/* "ms"=roomname\1nr_jids\1jid0\1..\1jidN */
groupname
,
CP_FLD_TERM
,
nr_usernames
);
/* add usernames */
for
(
i
=
0
;
i
<
nr_usernames
;
i
++
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
usernames
[
i
]
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_GRPCHAT_CREATE
);
}
/*------------------------------------------------------------------------
* Send packet to invite users to existing MultiMX room.
*
* @param session The MXit session object
* @param roomid The unique RoomID for the MultiMx room.
* @param nr_usernames Number of users being invited
* @param usernames The usernames of the users being invited
*/
void
mxit_send_groupchat_invite
(
struct
MXitSession
*
session
,
const
char
*
roomid
,
int
nr_usernames
,
const
char
*
usernames
[]
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
;
int
i
;
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms=%s%c%i"
,
/* "ms"=roomid\1nr_jids\1jid0\1..\1jidN */
roomid
,
CP_FLD_TERM
,
nr_usernames
);
/* add usernames */
for
(
i
=
0
;
i
<
nr_usernames
;
i
++
)
datalen
+=
scnprintf
(
data
+
datalen
,
sizeof
(
data
)
-
datalen
,
"%c%s"
,
CP_FLD_TERM
,
usernames
[
i
]
);
/* queue packet for transmission */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_GRPCHAT_INVITE
);
}
/*------------------------------------------------------------------------
* Send a "send file direct" multimedia packet.
*
* @param session The MXit session object
* @param username The username of the recipient
* @param filename The name of the file being sent
* @param buf The content of the file
* @param buflen The length of the file contents
*/
void
mxit_send_file
(
struct
MXitSession
*
session
,
const
char
*
username
,
const
char
*
filename
,
const
unsigned
char
*
buf
,
size_t
buflen
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"SENDING FILE '%s' of %zu bytes to user '%s'
\n
"
,
filename
,
buflen
,
username
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_senddirect
(
chunk_data
(
chunk
),
username
,
filename
,
buf
,
buflen
);
set_chunk_type
(
chunk
,
CP_CHUNK_DIRECT_SND
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Send a "reject file" multimedia packet.
*
* @param session The MXit session object
* @param fileid A unique ID that identifies this file
*/
void
mxit_send_file_reject
(
struct
MXitSession
*
session
,
const
char
*
fileid
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_send_file_reject
\n
"
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_reject
(
chunk_data
(
chunk
),
fileid
);
set_chunk_type
(
chunk
,
CP_CHUNK_REJECT
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Send a "get file" multimedia packet.
*
* @param session The MXit session object
* @param fileid A unique ID that identifies this file
* @param filesize The number of bytes to retrieve
* @param offset Offset in file at which to start retrieving
*/
void
mxit_send_file_accept
(
struct
MXitSession
*
session
,
const
char
*
fileid
,
size_t
filesize
,
size_t
offset
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_send_file_accept
\n
"
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_get
(
chunk_data
(
chunk
),
fileid
,
filesize
,
offset
);
set_chunk_type
(
chunk
,
CP_CHUNK_GET
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Send a "received file" multimedia packet.
*
* @param session The MXit session object
* @param status The status of the file-transfer
*/
void
mxit_send_file_received
(
struct
MXitSession
*
session
,
const
char
*
fileid
,
short
status
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_send_file_received
\n
"
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_received
(
chunk_data
(
chunk
),
fileid
,
status
);
set_chunk_type
(
chunk
,
CP_CHUNK_RECEIVED
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Send a "set avatar" multimedia packet.
*
* @param session The MXit session object
* @param data The avatar data
* @param buflen The length of the avatar data
*/
void
mxit_set_avatar
(
struct
MXitSession
*
session
,
const
unsigned
char
*
avatar
,
size_t
avatarlen
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_set_avatar: %zu bytes
\n
"
,
avatarlen
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_set_avatar
(
chunk_data
(
chunk
),
avatar
,
avatarlen
);
set_chunk_type
(
chunk
,
CP_CHUNK_SET_AVATAR
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Send a "get avatar" multimedia packet.
*
* @param session The MXit session object
* @param mxitId The username who's avatar to request
* @param avatarId The id of the avatar image (as string)
* @param data The avatar data
* @param buflen The length of the avatar data
*/
void
mxit_get_avatar
(
struct
MXitSession
*
session
,
const
char
*
mxitId
,
const
char
*
avatarId
)
{
char
data
[
CP_MAX_PACKET
];
int
datalen
=
0
;
gchar
*
chunk
;
size_t
chunksize
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_get_avatar: %s
\n
"
,
mxitId
);
/* convert the packet to a byte stream */
datalen
=
scnprintf
(
data
,
sizeof
(
data
),
"ms="
);
/* map chunk header over data buffer */
chunk
=
&
data
[
datalen
];
/* encode chunk */
chunksize
=
mxit_chunk_create_get_avatar
(
chunk_data
(
chunk
),
mxitId
,
avatarId
);
set_chunk_type
(
chunk
,
CP_CHUNK_GET_AVATAR
);
set_chunk_length
(
chunk
,
chunksize
);
datalen
+=
MXIT_CHUNK_HEADER_SIZE
+
chunksize
;
/* send the byte stream to the mxit server */
mxit_queue_packet
(
session
,
data
,
datalen
,
CP_CMD_MEDIA
);
}
/*------------------------------------------------------------------------
* Process a login message packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_login
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
PurpleStatus
*
status
;
int
presence
;
const
char
*
statusmsg
;
const
char
*
profilelist
[]
=
{
CP_PROFILE_BIRTHDATE
,
CP_PROFILE_GENDER
,
CP_PROFILE_FULLNAME
,
CP_PROFILE_TITLE
,
CP_PROFILE_FIRSTNAME
,
CP_PROFILE_LASTNAME
,
CP_PROFILE_EMAIL
,
CP_PROFILE_MOBILENR
,
CP_PROFILE_WHEREAMI
,
CP_PROFILE_ABOUTME
,
CP_PROFILE_RELATIONSHIP
,
CP_PROFILE_FLAGS
};
purple_account_set_int
(
session
->
acc
,
MXIT_CONFIG_STATE
,
MXIT_STATE_LOGIN
);
/* we were not yet logged in so we need to complete the login sequence here */
session
->
flags
|=
MXIT_FLAG_LOGGEDIN
;
purple_connection_update_progress
(
session
->
con
,
_
(
"Successfully Logged In..."
),
3
,
4
);
purple_connection_set_state
(
session
->
con
,
PURPLE_CONNECTION_CONNECTED
);
/* save extra info if this is a HTTP connection */
if
(
session
->
http
)
{
/* save the http server to use for this session */
g_strlcpy
(
session
->
http_server
,
records
[
1
]
->
fields
[
3
]
->
data
,
sizeof
(
session
->
http_server
)
);
/* save the session id */
session
->
http_sesid
=
atoi
(
records
[
0
]
->
fields
[
0
]
->
data
);
}
/* extract UserId (from protocol 5.9) */
if
(
records
[
1
]
->
fcount
>=
9
)
session
->
uid
=
g_strdup
(
records
[
1
]
->
fields
[
8
]
->
data
);
/* display the current splash-screen */
if
(
splash_popup_enabled
(
session
)
)
splash_display
(
session
);
/* update presence status */
status
=
purple_account_get_active_status
(
session
->
acc
);
presence
=
mxit_convert_presence
(
purple_status_get_id
(
status
)
);
statusmsg
=
purple_status_get_attr_string
(
status
,
"message"
);
if
(
(
presence
!=
MXIT_PRESENCE_ONLINE
)
||
(
statusmsg
)
)
{
/* when logging into MXit, your default presence is online. but with the UI, one can change
* the presence to whatever. in the case where its changed to a different presence setting
* we need to send an update to the server, otherwise the user's presence will be out of
* sync between the UI and MXit.
*/
char
*
statusmsg1
=
purple_markup_strip_html
(
statusmsg
);
char
*
statusmsg2
=
g_strndup
(
statusmsg1
,
CP_MAX_STATUS_MSG
);
mxit_send_presence
(
session
,
presence
,
statusmsg2
);
g_free
(
statusmsg1
);
g_free
(
statusmsg2
);
}
/* retrieve our MXit profile */
mxit_send_extprofile_request
(
session
,
NULL
,
ARRAY_SIZE
(
profilelist
),
profilelist
);
}
/*------------------------------------------------------------------------
* Process a received message packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_message
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
struct
RXMsgData
*
mx
=
NULL
;
char
*
message
=
NULL
;
char
*
sender
=
NULL
;
int
msglen
=
0
;
int
msgflags
=
0
;
int
msgtype
=
0
;
if
(
(
rcount
==
1
)
||
(
records
[
0
]
->
fcount
<
2
)
||
(
records
[
1
]
->
fcount
==
0
)
||
(
records
[
1
]
->
fields
[
0
]
->
len
==
0
)
)
{
/* packet contains no message or an empty message */
return
;
}
message
=
records
[
1
]
->
fields
[
0
]
->
data
;
msglen
=
strlen
(
message
);
/* strip off dummy domain */
sender
=
records
[
0
]
->
fields
[
0
]
->
data
;
mxit_strip_domain
(
sender
);
#ifdef DEBUG_PROTOCOL
purple_debug_info
(
MXIT_PLUGIN_ID
,
"Message received from '%s'
\n
"
,
sender
);
#endif
/* decode message flags (if any) */
if
(
records
[
0
]
->
fcount
>=
5
)
msgflags
=
atoi
(
records
[
0
]
->
fields
[
4
]
->
data
);
msgtype
=
atoi
(
records
[
0
]
->
fields
[
2
]
->
data
);
if
(
msgflags
&
CP_MSG_PWD_ENCRYPTED
)
{
/* this is a password encrypted message. we do not currently support those so ignore it */
PurpleBuddy
*
buddy
;
const
char
*
name
;
char
msg
[
128
];
buddy
=
purple_blist_find_buddy
(
session
->
acc
,
sender
);
if
(
buddy
)
name
=
purple_buddy_get_alias
(
buddy
);
else
name
=
sender
;
g_snprintf
(
msg
,
sizeof
(
msg
),
_
(
"%s sent you an encrypted message, but it is not supported on this client."
),
name
);
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Message Error"
),
msg
);
return
;
}
else
if
(
msgflags
&
CP_MSG_TL_ENCRYPTED
)
{
/* This is a transport-layer encrypted message. We don't support
* it anymore, because original client doesn't look like it was. */
purple_serv_got_im
(
session
->
con
,
sender
,
_
(
"An encrypted message was received which could not be decrypted."
),
PURPLE_MESSAGE_ERROR
,
time
(
NULL
));
return
;
}
if
(
msgflags
&
CP_MSG_NOTIFY_DELIVERY
)
{
/* delivery notification is requested */
if
(
records
[
0
]
->
fcount
>=
4
)
mxit_send_msgevent
(
session
,
sender
,
records
[
0
]
->
fields
[
3
]
->
data
,
CP_MSGEVENT_DELIVERED
);
}
/* create and initialise new markup struct */
mx
=
g_new0
(
struct
RXMsgData
,
1
);
mx
->
msg
=
g_string_sized_new
(
msglen
);
mx
->
session
=
session
;
mx
->
from
=
g_strdup
(
sender
);
mx
->
timestamp
=
atoi
(
records
[
0
]
->
fields
[
1
]
->
data
);
mx
->
got_img
=
FALSE
;
mx
->
chatid
=
-1
;
mx
->
img_count
=
0
;
/* update list of active chats */
if
(
!
find_active_chat
(
session
->
active_chats
,
mx
->
from
)
)
{
session
->
active_chats
=
g_list_append
(
session
->
active_chats
,
g_strdup
(
mx
->
from
)
);
}
if
(
is_multimx_contact
(
session
,
mx
->
from
)
)
{
/* this is a MultiMx chatroom message */
multimx_message_received
(
mx
,
message
,
msglen
,
msgtype
,
msgflags
);
}
else
{
mxit_parse_markup
(
mx
,
message
,
msglen
,
msgtype
,
msgflags
);
}
/* we are now done parsing the message */
mx
->
converted
=
TRUE
;
if
(
mx
->
img_count
==
0
)
{
/* we have all the data we need for this message to be displayed now. */
mxit_show_message
(
mx
);
}
else
{
/* this means there are still images outstanding for this message and
* still need to wait for them before we can display the message.
* so the image received callback function will eventually display
* the message. */
}
/* cleanup */
if
(
msgflags
&
CP_MSG_TL_ENCRYPTED
)
g_free
(
message
);
}
/*------------------------------------------------------------------------
* Process a received subscription request packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_new_sub
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
struct
contact
*
contact
;
struct
record
*
rec
;
int
i
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_parse_cmd_new_sub (%i recs)
\n
"
,
rcount
);
for
(
i
=
0
;
i
<
rcount
;
i
++
)
{
rec
=
records
[
i
];
if
(
rec
->
fcount
<
4
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"BAD SUBSCRIPTION RECORD! %i fields
\n
"
,
rec
->
fcount
);
break
;
}
/* build up a new contact info struct */
contact
=
g_new0
(
struct
contact
,
1
);
g_strlcpy
(
contact
->
username
,
rec
->
fields
[
0
]
->
data
,
sizeof
(
contact
->
username
)
);
mxit_strip_domain
(
contact
->
username
);
/* remove dummy domain */
g_strlcpy
(
contact
->
alias
,
rec
->
fields
[
1
]
->
data
,
sizeof
(
contact
->
alias
)
);
contact
->
type
=
atoi
(
rec
->
fields
[
2
]
->
data
);
if
(
rec
->
fcount
>=
5
)
{
/* there is a personal invite message attached */
if
(
(
rec
->
fields
[
4
]
->
data
)
&&
(
*
rec
->
fields
[
4
]
->
data
)
)
contact
->
msg
=
strdup
(
rec
->
fields
[
4
]
->
data
);
}
/* handle the subscription */
if
(
contact
->
type
==
MXIT_TYPE_MULTIMX
)
{
/* subscription to a MultiMX room */
char
*
creator
=
NULL
;
if
(
rec
->
fcount
>=
6
)
creator
=
rec
->
fields
[
5
]
->
data
;
multimx_invite
(
session
,
contact
,
creator
);
}
else
mxit_new_subscription
(
session
,
contact
);
}
}
/*------------------------------------------------------------------------
* Parse the received presence value, and ensure that it is supported.
*
* @param value The received presence value.
* @return A valid presence value.
*/
static
short
mxit_parse_presence
(
const
char
*
value
)
{
short
presence
=
atoi
(
value
);
/* ensure that the presence value is valid */
switch
(
presence
)
{
case
MXIT_PRESENCE_OFFLINE
:
case
MXIT_PRESENCE_ONLINE
:
case
MXIT_PRESENCE_AWAY
:
case
MXIT_PRESENCE_DND
:
return
presence
;
default
:
return
MXIT_PRESENCE_ONLINE
;
}
}
/*------------------------------------------------------------------------
* Parse the received mood value, and ensure that it is supported.
*
* @param value The received mood value.
* @return A valid mood value.
*/
static
short
mxit_parse_mood
(
const
char
*
value
)
{
short
mood
=
atoi
(
value
);
/* ensure that the mood value is valid */
if
(
(
mood
>=
MXIT_MOOD_NONE
)
&&
(
mood
<=
MXIT_MOOD_STRESSED
)
)
return
mood
;
return
MXIT_MOOD_NONE
;
}
/*------------------------------------------------------------------------
* Process a received contact update packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_contact
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
struct
contact
*
contact
=
NULL
;
struct
record
*
rec
;
int
i
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_parse_cmd_contact (%i recs)
\n
"
,
rcount
);
for
(
i
=
0
;
i
<
rcount
;
i
++
)
{
rec
=
records
[
i
];
if
(
rec
->
fcount
<
6
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"BAD CONTACT RECORD! %i fields
\n
"
,
rec
->
fcount
);
break
;
}
/* build up a new contact info struct */
contact
=
g_new0
(
struct
contact
,
1
);
g_strlcpy
(
contact
->
groupname
,
rec
->
fields
[
0
]
->
data
,
sizeof
(
contact
->
groupname
)
);
g_strlcpy
(
contact
->
username
,
rec
->
fields
[
1
]
->
data
,
sizeof
(
contact
->
username
)
);
mxit_strip_domain
(
contact
->
username
);
/* remove dummy domain */
g_strlcpy
(
contact
->
alias
,
rec
->
fields
[
2
]
->
data
,
sizeof
(
contact
->
alias
)
);
contact
->
presence
=
mxit_parse_presence
(
rec
->
fields
[
3
]
->
data
);
contact
->
type
=
atoi
(
rec
->
fields
[
4
]
->
data
);
contact
->
mood
=
mxit_parse_mood
(
rec
->
fields
[
5
]
->
data
);
if
(
rec
->
fcount
>
6
)
{
/* added in protocol 5.9 - flags & subtype */
contact
->
flags
=
atoi
(
rec
->
fields
[
6
]
->
data
);
contact
->
subtype
=
rec
->
fields
[
7
]
->
data
[
0
];
}
if
(
rec
->
fcount
>
8
)
{
/* added in protocol 6.0 - reject message */
contact
->
msg
=
g_strdup
(
rec
->
fields
[
8
]
->
data
);
}
/* add the contact to the buddy list */
if
(
contact
->
type
==
MXIT_TYPE_MULTIMX
)
/* contact is a MultiMX room */
multimx_created
(
session
,
contact
);
else
mxit_update_contact
(
session
,
contact
);
}
if
(
!
(
session
->
flags
&
MXIT_FLAG_FIRSTROSTER
)
)
{
session
->
flags
|=
MXIT_FLAG_FIRSTROSTER
;
mxit_update_blist
(
session
);
}
}
/*------------------------------------------------------------------------
* Process a received presence update packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_presence
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
int
i
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_parse_cmd_presence (%i recs)
\n
"
,
rcount
);
for
(
i
=
0
;
i
<
rcount
;
i
++
)
{
struct
record
*
rec
=
records
[
i
];
int
flags
=
0
;
if
(
rec
->
fcount
<
6
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"BAD PRESENCE RECORD! %i fields
\n
"
,
rec
->
fcount
);
break
;
}
/*
* The format of the record is:
* contactAddressN \1 presenceN \1 moodN \1 customMoodN \1 statusMsgN \1 avatarIdN [ \1 flagsN ]
*/
mxit_strip_domain
(
rec
->
fields
[
0
]
->
data
);
/* contactAddress */
if
(
rec
->
fcount
>=
7
)
/* flags field is included */
flags
=
atoi
(
rec
->
fields
[
6
]
->
data
);
mxit_update_buddy_presence
(
session
,
rec
->
fields
[
0
]
->
data
,
mxit_parse_presence
(
rec
->
fields
[
1
]
->
data
),
mxit_parse_mood
(
rec
->
fields
[
2
]
->
data
),
rec
->
fields
[
3
]
->
data
,
rec
->
fields
[
4
]
->
data
,
flags
);
mxit_update_buddy_avatar
(
session
,
rec
->
fields
[
0
]
->
data
,
rec
->
fields
[
5
]
->
data
);
}
}
/*------------------------------------------------------------------------
* Process a received extended profile packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_extprofile
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
const
char
*
mxitId
=
records
[
0
]
->
fields
[
0
]
->
data
;
struct
MXitProfile
*
profile
=
NULL
;
int
count
;
int
i
;
const
char
*
avatarId
=
NULL
;
char
*
statusMsg
=
NULL
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_parse_cmd_extprofile: profile for '%s'
\n
"
,
mxitId
);
if
(
(
records
[
0
]
->
fields
[
0
]
->
len
==
0
)
||
(
session
->
uid
&&
(
strcmp
(
session
->
uid
,
records
[
0
]
->
fields
[
0
]
->
data
)
==
0
)
)
)
{
/* No UserId or Our UserId provided, so this must be our own profile information */
if
(
session
->
profile
==
NULL
)
session
->
profile
=
g_new0
(
struct
MXitProfile
,
1
);
profile
=
session
->
profile
;
}
else
{
/* is a buddy's profile */
profile
=
g_new0
(
struct
MXitProfile
,
1
);
}
/* set the count for attributes */
count
=
atoi
(
records
[
0
]
->
fields
[
1
]
->
data
);
/* ensure the packet has the correct number of fields */
if
(
records
[
0
]
->
fcount
<
(
2
+
(
count
*
3
)
)
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Insufficient number of fields in extprofile response. fields=%i records=%i"
,
records
[
0
]
->
fcount
,
count
);
return
;
}
for
(
i
=
0
;
i
<
count
;
i
++
)
{
char
*
fname
;
char
*
fvalue
;
char
*
fstatus
;
int
f
=
(
i
*
3
)
+
2
;
fname
=
records
[
0
]
->
fields
[
f
]
->
data
;
/* field name */
fvalue
=
records
[
0
]
->
fields
[
f
+
1
]
->
data
;
/* field value */
fstatus
=
records
[
0
]
->
fields
[
f
+
2
]
->
data
;
/* field status */
/* first check the status on the returned attribute */
if
(
fstatus
[
0
]
!=
'0'
)
{
/* error: attribute requested was NOT found */
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Bad profile status on attribute '%s'
\n
"
,
fname
);
continue
;
}
if
(
strcmp
(
CP_PROFILE_BIRTHDATE
,
fname
)
==
0
)
{
/* birthdate */
if
(
records
[
0
]
->
fields
[
f
+
1
]
->
len
>
10
)
{
fvalue
[
10
]
=
'\0'
;
records
[
0
]
->
fields
[
f
+
1
]
->
len
=
10
;
}
memcpy
(
profile
->
birthday
,
fvalue
,
records
[
0
]
->
fields
[
f
+
1
]
->
len
);
}
else
if
(
strcmp
(
CP_PROFILE_GENDER
,
fname
)
==
0
)
{
/* gender */
profile
->
male
=
(
fvalue
[
0
]
==
'1'
);
}
else
if
(
strcmp
(
CP_PROFILE_FULLNAME
,
fname
)
==
0
)
{
/* nickname */
g_strlcpy
(
profile
->
nickname
,
fvalue
,
sizeof
(
profile
->
nickname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_STATUS
,
fname
)
==
0
)
{
/* status message - just keep a reference to the value */
statusMsg
=
g_markup_escape_text
(
fvalue
,
-1
);
}
else
if
(
strcmp
(
CP_PROFILE_AVATAR
,
fname
)
==
0
)
{
/* avatar id - just keep a reference to the value */
avatarId
=
fvalue
;
}
else
if
(
strcmp
(
CP_PROFILE_TITLE
,
fname
)
==
0
)
{
/* title */
g_strlcpy
(
profile
->
title
,
fvalue
,
sizeof
(
profile
->
title
)
);
}
else
if
(
strcmp
(
CP_PROFILE_FIRSTNAME
,
fname
)
==
0
)
{
/* first name */
g_strlcpy
(
profile
->
firstname
,
fvalue
,
sizeof
(
profile
->
firstname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_LASTNAME
,
fname
)
==
0
)
{
/* last name */
g_strlcpy
(
profile
->
lastname
,
fvalue
,
sizeof
(
profile
->
lastname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_EMAIL
,
fname
)
==
0
)
{
/* email address */
g_strlcpy
(
profile
->
email
,
fvalue
,
sizeof
(
profile
->
email
)
);
}
else
if
(
strcmp
(
CP_PROFILE_MOBILENR
,
fname
)
==
0
)
{
/* mobile number */
g_strlcpy
(
profile
->
mobilenr
,
fvalue
,
sizeof
(
profile
->
mobilenr
)
);
}
else
if
(
strcmp
(
CP_PROFILE_REGCOUNTRY
,
fname
)
==
0
)
{
/* registered country */
g_strlcpy
(
profile
->
regcountry
,
fvalue
,
sizeof
(
profile
->
regcountry
)
);
}
else
if
(
strcmp
(
CP_PROFILE_FLAGS
,
fname
)
==
0
)
{
/* profile flags */
profile
->
flags
=
g_ascii_strtoll
(
fvalue
,
NULL
,
10
);
}
else
if
(
strcmp
(
CP_PROFILE_LASTSEEN
,
fname
)
==
0
)
{
/* last seen online */
profile
->
lastonline
=
g_ascii_strtoll
(
fvalue
,
NULL
,
10
);
}
else
if
(
strcmp
(
CP_PROFILE_WHEREAMI
,
fname
)
==
0
)
{
/* where am I */
g_strlcpy
(
profile
->
whereami
,
fvalue
,
sizeof
(
profile
->
whereami
)
);
}
else
if
(
strcmp
(
CP_PROFILE_ABOUTME
,
fname
)
==
0
)
{
/* about me */
g_strlcpy
(
profile
->
aboutme
,
fvalue
,
sizeof
(
profile
->
aboutme
)
);
}
else
if
(
strcmp
(
CP_PROFILE_RELATIONSHIP
,
fname
)
==
0
)
{
/* relatinship status */
profile
->
relationship
=
strtol
(
fvalue
,
NULL
,
10
);
}
else
{
/* invalid profile attribute */
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Invalid profile attribute received '%s'
\n
"
,
fname
);
}
}
if
(
profile
!=
session
->
profile
)
{
/* not our own profile */
struct
contact
*
contact
=
NULL
;
contact
=
get_mxit_invite_contact
(
session
,
mxitId
);
if
(
contact
)
{
/* this is an invite, so update its profile info */
if
(
(
statusMsg
)
&&
(
*
statusMsg
)
)
{
/* update the status message */
g_free
(
contact
->
statusMsg
);
contact
->
statusMsg
=
strdup
(
statusMsg
);
}
else
contact
->
statusMsg
=
NULL
;
g_free
(
contact
->
profile
);
contact
->
profile
=
profile
;
if
(
(
avatarId
)
&&
(
*
avatarId
)
)
{
/* avatar must be requested for this invite before we can display it */
mxit_get_avatar
(
session
,
mxitId
,
avatarId
);
g_free
(
contact
->
avatarId
);
contact
->
avatarId
=
strdup
(
avatarId
);
}
else
{
/* display what we have */
contact
->
avatarId
=
NULL
;
mxit_show_profile
(
session
,
mxitId
,
profile
);
}
}
else
{
/* this is a contact */
if
(
avatarId
)
mxit_update_buddy_avatar
(
session
,
mxitId
,
avatarId
);
if
(
(
statusMsg
)
&&
(
*
statusMsg
)
)
{
/* update the status message */
PurpleBuddy
*
buddy
=
NULL
;
buddy
=
purple_blist_find_buddy
(
session
->
acc
,
mxitId
);
if
(
buddy
)
{
contact
=
purple_buddy_get_protocol_data
(
buddy
);
if
(
contact
)
{
g_free
(
contact
->
statusMsg
);
contact
->
statusMsg
=
strdup
(
statusMsg
);
}
}
}
/* show the profile */
mxit_show_profile
(
session
,
mxitId
,
profile
);
g_free
(
profile
);
}
}
g_free
(
statusMsg
);
}
/*------------------------------------------------------------------------
* Process a received suggest-contacts packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_suggestcontacts
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
GList
*
entries
=
NULL
;
int
searchType
;
int
maxResults
;
int
count
;
int
i
;
/*
* searchType \1 numSuggestions \1 total \1 numAttributes \1 name0 \1 name1 \1 ... \1 nameN \0
* userid \1 contactType \1 value0 \1 value1 ... valueN \0
* ...
* userid \1 contactType \1 value0 \1 value1 ... valueN
*/
/* ensure that record[0] contacts the minumum number of fields */
if
(
records
[
0
]
->
fcount
<
4
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Insufficient number of fields in suggest contacts response. fields=%i"
,
records
[
0
]
->
fcount
);
return
;
}
/* the type of results */
searchType
=
atoi
(
records
[
0
]
->
fields
[
0
]
->
data
);
/* the maximum number of results */
maxResults
=
atoi
(
records
[
0
]
->
fields
[
2
]
->
data
);
/* set the count for attributes */
count
=
atoi
(
records
[
0
]
->
fields
[
3
]
->
data
);
/* ensure that record[0] contains the specified number of attributes */
if
(
records
[
0
]
->
fcount
<
(
4
+
count
)
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Insufficient number of fields in suggest contacts response. fields=%i attributes=%i"
,
records
[
0
]
->
fcount
,
count
);
return
;
}
for
(
i
=
1
;
i
<
rcount
;
i
++
)
{
struct
record
*
rec
=
records
[
i
];
struct
MXitProfile
*
profile
=
g_new0
(
struct
MXitProfile
,
1
);
int
j
;
/* ensure that each result contains the specified number of attributes */
if
(
rec
->
fcount
!=
(
2
+
count
)
)
{
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Insufficient number of fields in suggest contacts response. fields=%i attributes=%i"
,
rec
->
fcount
,
count
);
g_free
(
profile
);
continue
;
}
g_strlcpy
(
profile
->
userid
,
rec
->
fields
[
0
]
->
data
,
sizeof
(
profile
->
userid
)
);
// TODO: ContactType - User or Service
for
(
j
=
0
;
j
<
count
;
j
++
)
{
char
*
fname
;
char
*
fvalue
=
""
;
fname
=
records
[
0
]
->
fields
[
4
+
j
]
->
data
;
/* field name */
if
(
records
[
i
]
->
fcount
>
(
2
+
j
)
)
fvalue
=
records
[
i
]
->
fields
[
2
+
j
]
->
data
;
/* field value */
purple_debug_info
(
MXIT_PLUGIN_ID
,
" %s: field='%s' value='%s'
\n
"
,
profile
->
userid
,
fname
,
fvalue
);
if
(
strcmp
(
CP_PROFILE_BIRTHDATE
,
fname
)
==
0
)
{
/* birthdate */
g_strlcpy
(
profile
->
birthday
,
fvalue
,
sizeof
(
profile
->
birthday
)
);
}
else
if
(
strcmp
(
CP_PROFILE_FIRSTNAME
,
fname
)
==
0
)
{
/* first name */
g_strlcpy
(
profile
->
firstname
,
fvalue
,
sizeof
(
profile
->
firstname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_LASTNAME
,
fname
)
==
0
)
{
/* last name */
g_strlcpy
(
profile
->
lastname
,
fvalue
,
sizeof
(
profile
->
lastname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_GENDER
,
fname
)
==
0
)
{
/* gender */
profile
->
male
=
(
fvalue
[
0
]
==
'1'
);
}
else
if
(
strcmp
(
CP_PROFILE_FULLNAME
,
fname
)
==
0
)
{
/* nickname */
g_strlcpy
(
profile
->
nickname
,
fvalue
,
sizeof
(
profile
->
nickname
)
);
}
else
if
(
strcmp
(
CP_PROFILE_WHEREAMI
,
fname
)
==
0
)
{
/* where am I */
g_strlcpy
(
profile
->
whereami
,
fvalue
,
sizeof
(
profile
->
whereami
)
);
}
/* ignore other attibutes */
}
entries
=
g_list_append
(
entries
,
profile
);
}
/* display */
mxit_show_search_results
(
session
,
searchType
,
maxResults
,
entries
);
/* cleanup */
g_list_foreach
(
entries
,
(
GFunc
)
g_free
,
NULL
);
}
/*------------------------------------------------------------------------
* Process a received message event packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_msgevent
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
int
event
;
/*
* contactAddress \1 dateTime \1 id \1 event
*/
/* strip off dummy domain */
mxit_strip_domain
(
records
[
0
]
->
fields
[
0
]
->
data
);
event
=
atoi
(
records
[
0
]
->
fields
[
3
]
->
data
);
switch
(
event
)
{
case
CP_MSGEVENT_TYPING
:
/* user is typing */
case
CP_MSGEVENT_ANGRY
:
/* user is typing angrily */
purple_serv_got_typing
(
session
->
con
,
records
[
0
]
->
fields
[
0
]
->
data
,
0
,
PURPLE_IM_TYPING
);
break
;
case
CP_MSGEVENT_STOPPED
:
/* user has stopped typing */
purple_serv_got_typing_stopped
(
session
->
con
,
records
[
0
]
->
fields
[
0
]
->
data
);
break
;
case
CP_MSGEVENT_ERASING
:
/* user is erasing text */
case
CP_MSGEVENT_DELIVERED
:
/* message was delivered */
case
CP_MSGEVENT_DISPLAYED
:
/* message was viewed */
/* these are currently not supported by libPurple */
break
;
default
:
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Unknown message event received (%i)
\n
"
,
event
);
}
}
/*------------------------------------------------------------------------
* Process a received multimedia packet.
*
* @param session The MXit session object
* @param records The packet's data records
* @param rcount The number of data records
*/
static
void
mxit_parse_cmd_media
(
struct
MXitSession
*
session
,
struct
record
**
records
,
int
rcount
)
{
guint
chunktype
;
guint32
chunksize
;
gchar
*
chunkdata
;
/* received packet is too short to even contain a chunk header */
if
(
records
[
0
]
->
fields
[
0
]
->
len
<
MXIT_CHUNK_HEADER_SIZE
)
return
;
/* decode the chunk header */
chunktype
=
chunk_type
(
records
[
0
]
->
fields
[
0
]
->
data
);
chunksize
=
chunk_length
(
records
[
0
]
->
fields
[
0
]
->
data
);
chunkdata
=
chunk_data
(
records
[
0
]
->
fields
[
0
]
->
data
);
/* check chunk size against length of received data */
if
(
MXIT_CHUNK_HEADER_SIZE
+
chunksize
>
records
[
0
]
->
fields
[
0
]
->
len
)
return
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_parse_cmd_media (%i records) (%i type) (%i bytes)
\n
"
,
rcount
,
chunktype
,
chunksize
);
/* supported chunked data types */
switch
(
chunktype
)
{
case
CP_CHUNK_CUSTOM
:
/* custom resource */
{
struct
cr_chunk
chunk
;
/* decode the chunked data */
if
(
mxit_chunk_parse_cr
(
chunkdata
,
chunksize
,
&
chunk
)
)
{
purple_debug_info
(
MXIT_PLUGIN_ID
,
"chunk info id=%s handle=%s op=%i
\n
"
,
chunk
.
id
,
chunk
.
handle
,
chunk
.
operation
);
/* this is a splash-screen operation */
if
(
strcmp
(
chunk
.
handle
,
HANDLE_SPLASH2
)
==
0
)
{
if
(
chunk
.
operation
==
CR_OP_UPDATE
)
{
/* update the splash-screen */
struct
splash_chunk
*
splash
=
chunk
.
resources
->
data
;
// TODO: Fix - assuming 1st resource is splash
gboolean
clickable
=
(
g_list_length
(
chunk
.
resources
)
>
1
);
// TODO: Fix - if 2 resources, then is clickable
if
(
splash
!=
NULL
)
splash_update
(
session
,
chunk
.
id
,
splash
->
data
,
splash
->
datalen
,
clickable
);
}
else
if
(
chunk
.
operation
==
CR_OP_REMOVE
)
/* remove the splash-screen */
splash_remove
(
session
);
}
/* cleanup custom resources */
g_list_foreach
(
chunk
.
resources
,
(
GFunc
)
g_free
,
NULL
);
}
}
break
;
case
CP_CHUNK_OFFER
:
/* file offer */
{
struct
offerfile_chunk
chunk
;
/* decode the chunked data */
if
(
mxit_chunk_parse_offer
(
chunkdata
,
chunksize
,
&
chunk
)
)
{
/* process the offer */
mxit_xfer_rx_offer
(
session
,
chunk
.
username
,
chunk
.
filename
,
chunk
.
filesize
,
chunk
.
fileid
);
}
}
break
;
case
CP_CHUNK_GET
:
/* get file response */
{
struct
getfile_chunk
chunk
;
/* decode the chunked data */
if
(
mxit_chunk_parse_get
(
chunkdata
,
chunksize
,
&
chunk
)
)
{
/* process the getfile */
mxit_xfer_rx_file
(
session
,
chunk
.
fileid
,
chunk
.
data
,
chunk
.
length
);
}
}
break
;
case
CP_CHUNK_GET_AVATAR
:
/* get avatars */
{
struct
getavatar_chunk
chunk
;
struct
contact
*
contact
=
NULL
;
/* decode the chunked data */
if
(
mxit_chunk_parse_get_avatar
(
chunkdata
,
chunksize
,
&
chunk
)
)
{
/* update avatar image */
purple_debug_info
(
MXIT_PLUGIN_ID
,
"updating avatar for contact '%s'
\n
"
,
chunk
.
mxitid
);
contact
=
get_mxit_invite_contact
(
session
,
chunk
.
mxitid
);
if
(
contact
)
{
/* this is an invite (add image to the internal image store) */
if
(
contact
->
image
)
g_object_unref
(
contact
->
image
);
contact
->
image
=
purple_image_new_from_data
(
g_memdup
(
chunk
.
data
,
chunk
.
length
),
chunk
.
length
);
/* show the profile */
mxit_show_profile
(
session
,
chunk
.
mxitid
,
contact
->
profile
);
}
else
{
/* this is a contact's avatar, so update it */
purple_buddy_icons_set_for_user
(
session
->
acc
,
chunk
.
mxitid
,
g_memdup
(
chunk
.
data
,
chunk
.
length
),
chunk
.
length
,
chunk
.
avatarid
);
}
}
}
break
;
case
CP_CHUNK_SET_AVATAR
:
/* this is a reply packet to a set avatar request. no action is required */
break
;
case
CP_CHUNK_REJECT
:
/* this is a reply packet to a reject file request. no action is required */
break
;
case
CP_CHUNK_DIRECT_SND
:
/* this is a ack for a file send. */
{
struct
sendfile_chunk
chunk
;
if
(
mxit_chunk_parse_sendfile
(
chunkdata
,
chunksize
,
&
chunk
)
)
{
purple_debug_info
(
MXIT_PLUGIN_ID
,
"file-send send to '%s' [status=%i message='%s']
\n
"
,
chunk
.
username
,
chunk
.
status
,
chunk
.
statusmsg
);
if
(
chunk
.
status
!=
0
)
/* not success */
mxit_popup
(
PURPLE_NOTIFY_MSG_ERROR
,
_
(
"File Send Failed"
),
chunk
.
statusmsg
);
}
}
break
;
case
CP_CHUNK_RECEIVED
:
/* this is a ack for a file received. no action is required */
break
;
default
:
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Unsupported chunked data packet type received (%i)
\n
"
,
chunktype
);
break
;
}
}
/*------------------------------------------------------------------------
* Handle a redirect sent from the MXit server.
*
* @param session The MXit session object
* @param url The redirect information
*/
static
void
mxit_perform_redirect
(
struct
MXitSession
*
session
,
const
char
*
url
)
{
gchar
**
parts
;
gchar
**
host
;
int
type
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_perform_redirect: %s
\n
"
,
url
);
/* tokenize the URL string */
parts
=
g_strsplit
(
url
,
";"
,
0
);
/* Part 1: protocol://host:port */
host
=
g_strsplit
(
parts
[
0
],
":"
,
4
);
if
(
strcmp
(
host
[
0
],
"socket"
)
==
0
)
{
/* redirect to a MXit socket proxy */
g_strlcpy
(
session
->
server
,
&
host
[
1
][
2
],
sizeof
(
session
->
server
)
);
session
->
port
=
atoi
(
host
[
2
]
);
}
else
{
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Cannot perform redirect using the specified protocol"
)
);
goto
redirect_fail
;
}
/* Part 2: type of redirect */
type
=
atoi
(
parts
[
1
]
);
if
(
type
==
CP_REDIRECT_PERMANENT
)
{
/* permanent redirect, so save new MXit server and port */
purple_account_set_string
(
session
->
acc
,
MXIT_CONFIG_SERVER_ADDR
,
session
->
server
);
purple_account_set_int
(
session
->
acc
,
MXIT_CONFIG_SERVER_PORT
,
session
->
port
);
}
/* Part 3: message (optional) */
if
(
parts
[
2
]
!=
NULL
)
purple_connection_notice
(
session
->
con
,
parts
[
2
]
);
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_perform_redirect: %s redirect to %s:%i
\n
"
,
(
type
==
CP_REDIRECT_PERMANENT
)
?
"Permanent"
:
"Temporary"
,
session
->
server
,
session
->
port
);
/* perform the re-connect to the new MXit server */
mxit_reconnect
(
session
);
redirect_fail
:
g_strfreev
(
parts
);
g_strfreev
(
host
);
}
/*------------------------------------------------------------------------
* Process a success response received from the MXit server.
*
* @param session The MXit session object
* @param packet The received packet
*/
static
int
process_success_response
(
struct
MXitSession
*
session
,
struct
rx_packet
*
packet
)
{
/* ignore ping/poll packets */
if
(
(
packet
->
cmd
!=
CP_CMD_PING
)
&&
(
packet
->
cmd
!=
CP_CMD_POLL
)
)
session
->
last_rx
=
mxit_now_milli
();
/*
* when we pass the packet records to the next level for parsing
* we minus 3 records because 1) the first record is the packet
* type 2) packet reply status 3) the last record is bogus
*/
/* packet command */
switch
(
packet
->
cmd
)
{
case
CP_CMD_REGISTER
:
/* fall through, when registeration successful, MXit will auto login */
case
CP_CMD_LOGIN
:
/* login response */
if
(
!
(
session
->
flags
&
MXIT_FLAG_LOGGEDIN
)
)
{
mxit_parse_cmd_login
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
3
);
}
break
;
case
CP_CMD_LOGOUT
:
/* logout response */
session
->
flags
&=
~
MXIT_FLAG_LOGGEDIN
;
purple_account_disconnect
(
session
->
acc
);
/* note:
* we do not prompt the user here for a reconnect, because this could be the user
* logging in with his phone. so we just disconnect the account otherwise
* mxit will start to bounce between the phone and pidgin. also could be a valid
* disconnect selected by the user.
*/
return
-1
;
case
CP_CMD_CONTACT
:
/* contact update */
mxit_parse_cmd_contact
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
3
);
break
;
case
CP_CMD_PRESENCE
:
/* presence update */
mxit_parse_cmd_presence
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
3
);
break
;
case
CP_CMD_RX_MSG
:
/* incoming message (no bogus record) */
mxit_parse_cmd_message
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
2
);
break
;
case
CP_CMD_NEW_SUB
:
/* new subscription request */
mxit_parse_cmd_new_sub
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
3
);
break
;
case
CP_CMD_MEDIA
:
/* multi-media message */
mxit_parse_cmd_media
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
2
);
break
;
case
CP_CMD_EXTPROFILE_GET
:
/* profile update */
mxit_parse_cmd_extprofile
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
2
);
break
;
case
CP_CMD_SUGGESTCONTACTS
:
/* suggest contacts */
mxit_parse_cmd_suggestcontacts
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
2
);
break
;
case
CP_CMD_GOT_MSGEVENT
:
/* received message event */
mxit_parse_cmd_msgevent
(
session
,
&
packet
->
records
[
2
],
packet
->
rcount
-
2
);
break
;
case
CP_CMD_MOOD
:
/* mood update */
case
CP_CMD_UPDATE
:
/* update contact information */
case
CP_CMD_ALLOW
:
/* allow subscription ack */
case
CP_CMD_DENY
:
/* deny subscription ack */
case
CP_CMD_INVITE
:
/* invite contact ack */
case
CP_CMD_REMOVE
:
/* remove contact ack */
case
CP_CMD_TX_MSG
:
/* outgoing message ack */
case
CP_CMD_STATUS
:
/* presence update ack */
case
CP_CMD_GRPCHAT_CREATE
:
/* create groupchat */
case
CP_CMD_GRPCHAT_INVITE
:
/* groupchat invite */
case
CP_CMD_PING
:
/* ping reply */
case
CP_CMD_POLL
:
/* HTTP poll reply */
case
CP_CMD_EXTPROFILE_SET
:
/* profile update */
// TODO: Protocol 6.2 indicates status for each attribute, and current value.
case
CP_CMD_SPLASHCLICK
:
/* splash-screen clickthrough */
case
CP_CMD_MSGEVENT
:
/* event message */
break
;
default
:
/* unknown packet */
purple_debug_error
(
MXIT_PLUGIN_ID
,
"Received unknown client packet (cmd = %i)
\n
"
,
packet
->
cmd
);
}
return
0
;
}
/*------------------------------------------------------------------------
* Process an error response received from the MXit server.
*
* @param session The MXit session object
* @param packet The received packet
*/
static
int
process_error_response
(
struct
MXitSession
*
session
,
struct
rx_packet
*
packet
)
{
char
errmsg
[
256
];
const
char
*
errdesc
;
/* set the error description to be shown to the user */
if
(
packet
->
errmsg
)
errdesc
=
packet
->
errmsg
;
else
errdesc
=
_
(
"An internal MXit server error occurred."
);
purple_debug_info
(
MXIT_PLUGIN_ID
,
"Error Reply %i:%s
\n
"
,
packet
->
errcode
,
errdesc
);
if
(
packet
->
errcode
==
MXIT_ERRCODE_LOGGEDOUT
)
{
/* we are not currently logged in, so we need to reconnect */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
errdesc
)
);
}
/* packet command */
switch
(
packet
->
cmd
)
{
case
CP_CMD_REGISTER
:
case
CP_CMD_LOGIN
:
if
(
packet
->
errcode
==
MXIT_ERRCODE_REDIRECT
)
{
mxit_perform_redirect
(
session
,
packet
->
errmsg
);
return
0
;
}
else
{
g_snprintf
(
errmsg
,
sizeof
(
errmsg
),
_
(
"Login error: %s (%i)"
),
errdesc
,
packet
->
errcode
);
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
errmsg
);
return
-1
;
}
case
CP_CMD_LOGOUT
:
g_snprintf
(
errmsg
,
sizeof
(
errmsg
),
_
(
"Logout error: %s (%i)"
),
errdesc
,
packet
->
errcode
);
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NAME_IN_USE
,
_
(
errmsg
)
);
return
-1
;
case
CP_CMD_CONTACT
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Contact Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_RX_MSG
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Message Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_TX_MSG
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Message Sending Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_STATUS
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Status Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_MOOD
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Mood Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_KICK
:
/*
* the MXit server sends this packet if we were idle for too long.
* to stop the server from closing this connection we need to resend
* the login packet.
*/
mxit_send_login
(
session
);
break
;
case
CP_CMD_INVITE
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Invitation Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_REMOVE
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Contact Removal Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_ALLOW
:
case
CP_CMD_DENY
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Subscription Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_UPDATE
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Contact Update Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_MEDIA
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"File Transfer Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_GRPCHAT_CREATE
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Cannot create MultiMx room"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_GRPCHAT_INVITE
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"MultiMx Invitation Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_EXTPROFILE_GET
:
case
CP_CMD_EXTPROFILE_SET
:
mxit_popup
(
PURPLE_NOTIFY_MSG_WARNING
,
_
(
"Profile Error"
),
_
(
errdesc
)
);
break
;
case
CP_CMD_SPLASHCLICK
:
case
CP_CMD_MSGEVENT
:
/* ignore error */
break
;
case
CP_CMD_PING
:
case
CP_CMD_POLL
:
break
;
default
:
mxit_popup
(
PURPLE_NOTIFY_MSG_ERROR
,
_
(
"Error"
),
_
(
errdesc
)
);
break
;
}
return
0
;
}
/*========================================================================================================================
* Low-level Packet receive
*/
#ifdef DEBUG_PROTOCOL
/*------------------------------------------------------------------------
* Dump a received packet structure.
*
* @param p The received packet
*/
static
void
dump_packet
(
struct
rx_packet
*
p
)
{
struct
record
*
r
=
NULL
;
struct
field
*
f
=
NULL
;
int
i
;
int
j
;
purple_debug_info
(
MXIT_PLUGIN_ID
,
"PACKET DUMP: (%i records)
\n
"
,
p
->
rcount
);
for
(
i
=
0
;
i
<
p
->
rcount
;
i
++
)
{
r
=
p
->
records
[
i
];
purple_debug_info
(
MXIT_PLUGIN_ID
,
"RECORD: (%i fields)
\n
"
,
r
->
fcount
);
for
(
j
=
0
;
j
<
r
->
fcount
;
j
++
)
{
f
=
r
->
fields
[
j
];
purple_debug_info
(
MXIT_PLUGIN_ID
,
"
\t
FIELD: (len=%zu) '%s'
\n
"
,
f
->
len
,
f
->
data
);
}
}
}
#endif
/*------------------------------------------------------------------------
* Free up memory used by a packet structure.
*
* @param p The received packet
*/
static
void
free_rx_packet
(
struct
rx_packet
*
p
)
{
struct
record
*
r
=
NULL
;
struct
field
*
f
=
NULL
;
int
i
;
int
j
;
for
(
i
=
0
;
i
<
p
->
rcount
;
i
++
)
{
r
=
p
->
records
[
i
];
for
(
j
=
0
;
j
<
r
->
fcount
;
j
++
)
{
g_free
(
f
);
}
g_free
(
r
->
fields
);
g_free
(
r
);
}
g_free
(
p
->
records
);
}
/*------------------------------------------------------------------------
* Add a new field to a record.
*
* @param r Parent record object
* @return The newly created field
*/
static
struct
field
*
add_field
(
struct
record
*
r
)
{
struct
field
*
field
;
field
=
g_new0
(
struct
field
,
1
);
r
->
fields
=
g_realloc
(
r
->
fields
,
sizeof
(
struct
field
*
)
*
(
r
->
fcount
+
1
)
);
r
->
fields
[
r
->
fcount
]
=
field
;
r
->
fcount
++
;
return
field
;
}
/*------------------------------------------------------------------------
* Add a new record to a packet.
*
* @param p The packet object
* @return The newly created record
*/
static
struct
record
*
add_record
(
struct
rx_packet
*
p
)
{
struct
record
*
rec
;
rec
=
g_new0
(
struct
record
,
1
);
p
->
records
=
g_realloc
(
p
->
records
,
sizeof
(
struct
record
*
)
*
(
p
->
rcount
+
1
)
);
p
->
records
[
p
->
rcount
]
=
rec
;
p
->
rcount
++
;
return
rec
;
}
/*------------------------------------------------------------------------
* Parse the received byte stream into a proper client protocol packet.
*
* @param session The MXit session object
* @return Success (0) or Failure (!0)
*/
int
mxit_parse_packet
(
struct
MXitSession
*
session
)
{
struct
rx_packet
packet
;
struct
record
*
rec
;
struct
field
*
field
;
gboolean
pbreak
;
unsigned
int
i
;
int
res
=
0
;
#ifdef DEBUG_PROTOCOL
purple_debug_info
(
MXIT_PLUGIN_ID
,
"Received packet (%i bytes)
\n
"
,
session
->
rx_i
);
dump_bytes
(
session
,
session
->
rx_dbuf
,
session
->
rx_i
);
#endif
i
=
0
;
while
(
i
<
session
->
rx_i
)
{
/* create first record and field */
rec
=
NULL
;
field
=
NULL
;
memset
(
&
packet
,
0x00
,
sizeof
(
struct
rx_packet
)
);
rec
=
add_record
(
&
packet
);
pbreak
=
FALSE
;
/* break up the received packet into fields and records for easy parsing */
while
(
(
i
<
session
->
rx_i
)
&&
(
!
pbreak
)
)
{
switch
(
session
->
rx_dbuf
[
i
]
)
{
case
CP_SOCK_REC_TERM
:
/* new record */
if
(
packet
.
rcount
==
1
)
{
/* packet command */
if
(
packet
.
records
[
0
]
->
fcount
>
0
)
packet
.
cmd
=
atoi
(
packet
.
records
[
0
]
->
fields
[
0
]
->
data
);
}
else
if
(
packet
.
rcount
==
2
)
{
/* special case: binary multimedia packets should not be parsed here */
if
(
packet
.
cmd
==
CP_CMD_MEDIA
)
{
/* add the chunked to new record */
rec
=
add_record
(
&
packet
);
field
=
add_field
(
rec
);
field
->
data
=
&
session
->
rx_dbuf
[
i
+
1
];
field
->
len
=
session
->
rx_i
-
i
;
/* now skip the binary data */
res
=
chunk_length
(
field
->
data
);
/* determine if we have more packets */
if
(
res
+
6
+
i
<
session
->
rx_i
)
{
/* we have more than one packet in this stream */
i
+=
res
+
6
;
pbreak
=
TRUE
;
}
else
{
i
=
session
->
rx_i
;
}
}
}
else
if
(
!
field
)
{
field
=
add_field
(
rec
);
field
->
data
=
&
session
->
rx_dbuf
[
i
];
}
session
->
rx_dbuf
[
i
]
=
'\0'
;
rec
=
add_record
(
&
packet
);
field
=
NULL
;
break
;
case
CP_FLD_TERM
:
/* new field */
session
->
rx_dbuf
[
i
]
=
'\0'
;
if
(
!
field
)
{
field
=
add_field
(
rec
);
field
->
data
=
&
session
->
rx_dbuf
[
i
];
}
field
=
NULL
;
break
;
case
CP_PKT_TERM
:
/* packet is done! */
session
->
rx_dbuf
[
i
]
=
'\0'
;
pbreak
=
TRUE
;
break
;
default
:
/* skip non special characters */
if
(
!
field
)
{
field
=
add_field
(
rec
);
field
->
data
=
&
session
->
rx_dbuf
[
i
];
}
field
->
len
++
;
break
;
}
i
++
;
}
if
(
packet
.
rcount
<
2
)
{
/* bad packet */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"Invalid packet received from MXit."
)
);
free_rx_packet
(
&
packet
);
continue
;
}
session
->
rx_dbuf
[
session
->
rx_i
]
=
'\0'
;
packet
.
errcode
=
atoi
(
packet
.
records
[
1
]
->
fields
[
0
]
->
data
);
purple_debug_info
(
MXIT_PLUGIN_ID
,
"Packet received CMD:%i (%i)
\n
"
,
packet
.
cmd
,
packet
.
errcode
);
#ifdef DEBUG_PROTOCOL
/* debug */
dump_packet
(
&
packet
);
#endif
/* reset the out ack */
if
(
session
->
outack
==
packet
.
cmd
)
{
/* outstanding ack received from mxit server */
session
->
outack
=
0
;
}
/* check packet status */
if
(
packet
.
errcode
!=
MXIT_ERRCODE_SUCCESS
)
{
/* error reply! */
if
(
(
packet
.
records
[
1
]
->
fcount
>
1
)
&&
(
packet
.
records
[
1
]
->
fields
[
1
]
->
data
)
)
packet
.
errmsg
=
packet
.
records
[
1
]
->
fields
[
1
]
->
data
;
else
packet
.
errmsg
=
NULL
;
res
=
process_error_response
(
session
,
&
packet
);
}
else
{
/* success reply! */
res
=
process_success_response
(
session
,
&
packet
);
}
/* free up the packet resources */
free_rx_packet
(
&
packet
);
}
if
(
session
->
outack
==
0
)
mxit_manage_queue
(
session
);
return
res
;
}
/*------------------------------------------------------------------------
* Callback when data is received from the MXit server.
*
* @param user_data The MXit session object
* @param source The file-descriptor on which data was received
* @param cond Condition which caused the callback (PURPLE_INPUT_READ)
*/
void
mxit_cb_rx
(
gpointer
user_data
,
gint
source
,
PurpleInputCondition
cond
)
{
struct
MXitSession
*
session
=
(
struct
MXitSession
*
)
user_data
;
char
ch
;
int
res
;
int
len
;
if
(
session
->
rx_state
==
RX_STATE_RLEN
)
{
/* we are reading in the packet length */
len
=
read
(
session
->
fd
,
&
ch
,
1
);
if
(
len
<
0
)
{
/* connection error */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x01)"
)
);
return
;
}
else
if
(
len
==
0
)
{
/* connection closed */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x02)"
)
);
return
;
}
else
{
/* byte read */
if
(
ch
==
CP_REC_TERM
)
{
/* the end of the length record found */
session
->
rx_lbuf
[
session
->
rx_i
]
=
'\0'
;
session
->
rx_res
=
atoi
(
&
session
->
rx_lbuf
[
3
]
);
if
(
(
session
->
rx_res
<=
0
)
||
(
session
->
rx_res
>
CP_MAX_PACKET
)
)
{
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x03)"
)
);
return
;
}
session
->
rx_state
=
RX_STATE_DATA
;
session
->
rx_i
=
0
;
}
else
{
/* still part of the packet length record */
session
->
rx_lbuf
[
session
->
rx_i
]
=
ch
;
session
->
rx_i
++
;
if
(
session
->
rx_i
>=
sizeof
(
session
->
rx_lbuf
)
)
{
/* malformed packet length record (too long) */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x04)"
)
);
return
;
}
}
}
}
else
if
(
session
->
rx_state
==
RX_STATE_DATA
)
{
/* we are reading in the packet data */
len
=
read
(
session
->
fd
,
&
session
->
rx_dbuf
[
session
->
rx_i
],
session
->
rx_res
);
if
(
len
<
0
)
{
/* connection error */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x05)"
)
);
return
;
}
else
if
(
len
==
0
)
{
/* connection closed */
purple_connection_error
(
session
->
con
,
PURPLE_CONNECTION_ERROR_NETWORK_ERROR
,
_
(
"A connection error occurred to MXit. (read stage 0x06)"
)
);
return
;
}
else
{
/* data read */
session
->
rx_i
+=
len
;
session
->
rx_res
-=
len
;
if
(
session
->
rx_res
==
0
)
{
/* ok, so now we have read in the whole packet */
session
->
rx_state
=
RX_STATE_PROC
;
}
}
}
if
(
session
->
rx_state
==
RX_STATE_PROC
)
{
/* we have a full packet, which we now need to process */
res
=
mxit_parse_packet
(
session
);
if
(
res
==
0
)
{
/* we are still logged in */
session
->
rx_state
=
RX_STATE_RLEN
;
session
->
rx_res
=
0
;
session
->
rx_i
=
0
;
}
}
}
/*------------------------------------------------------------------------
* Log the user off MXit and close the connection
*
* @param session The MXit session object
*/
void
mxit_close_connection
(
struct
MXitSession
*
session
)
{
purple_debug_info
(
MXIT_PLUGIN_ID
,
"mxit_close_connection
\n
"
);
if
(
!
(
session
->
flags
&
MXIT_FLAG_CONNECTED
)
)
{
/* we are already closed */
return
;
}
else
if
(
session
->
flags
&
MXIT_FLAG_LOGGEDIN
)
{
/* we are currently logged in so we need to send a logout packet */
if
(
!
session
->
http
)
{
mxit_send_logout
(
session
);
}
session
->
flags
&=
~
MXIT_FLAG_LOGGEDIN
;
}
session
->
flags
&=
~
MXIT_FLAG_CONNECTED
;
/* cancel all outstanding async calls */
purple_http_connection_set_destroy
(
session
->
async_http_reqs
);
session
->
async_http_reqs
=
NULL
;
/* remove the input cb function */
if
(
session
->
inpa
)
{
purple_input_remove
(
session
->
inpa
);
session
->
inpa
=
0
;
}
/* remove HTTP poll timer */
if
(
session
->
http_timer_id
>
0
)
purple_timeout_remove
(
session
->
http_timer_id
);
/* remove slow queue manager timer */
if
(
session
->
q_slow_timer_id
>
0
)
purple_timeout_remove
(
session
->
q_slow_timer_id
);
/* remove fast queue manager timer */
if
(
session
->
q_fast_timer_id
>
0
)
purple_timeout_remove
(
session
->
q_fast_timer_id
);
/* remove all groupchat rooms */
while
(
session
->
rooms
!=
NULL
)
{
struct
multimx
*
multimx
=
(
struct
multimx
*
)
session
->
rooms
->
data
;
session
->
rooms
=
g_list_remove
(
session
->
rooms
,
multimx
);
free
(
multimx
);
}
g_list_free
(
session
->
rooms
);
session
->
rooms
=
NULL
;
/* remove all rx chats names */
while
(
session
->
active_chats
!=
NULL
)
{
char
*
chat
=
(
char
*
)
session
->
active_chats
->
data
;
session
->
active_chats
=
g_list_remove
(
session
->
active_chats
,
chat
);
g_free
(
chat
);
}
g_list_free
(
session
->
active_chats
);
session
->
active_chats
=
NULL
;
/* clear the internal invites */
while
(
session
->
invites
!=
NULL
)
{
struct
contact
*
contact
=
(
struct
contact
*
)
session
->
invites
->
data
;
session
->
invites
=
g_list_remove
(
session
->
invites
,
contact
);
g_free
(
contact
->
msg
);
g_free
(
contact
->
statusMsg
);
g_free
(
contact
->
profile
);
if
(
contact
->
image
)
g_object_unref
(
contact
->
image
);
g_free
(
contact
);
}
g_list_free
(
session
->
invites
);
session
->
invites
=
NULL
;
free
(
session
->
profile
);
mxit_free_emoticon_cache
(
session
);
g_free
(
session
->
uid
);
g_free
(
session
->
encpwd
);
session
->
encpwd
=
NULL
;
/* flush all the commands still in the queue */
flush_queue
(
session
);
}