aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/msn
diff options
context:
space:
mode:
authorWilmer van der Gaast <wilmer@gaast.net>2010-08-08 16:34:49 +0100
committerWilmer van der Gaast <wilmer@gaast.net>2010-08-08 16:34:49 +0100
commit2528cdad90271f80d2ffe0e679ff8258f3e94e4c (patch)
tree9557a527a0aca1cae02806e7aff1d9df8e3e25a9 /protocols/msn
parentb8906261293b34d8c792bd1f48df10144a8a8f10 (diff)
parentee6cc946dc4ee82cb641df94a6ba101e99253af2 (diff)
Merging msn-offline branch. A tiny bit of MSNP13, and it works for the first
minute of the session (after that the MSN server finds out the rest of BitlBee still speaks MSNP8).
Diffstat (limited to 'protocols/msn')
-rw-r--r--protocols/msn/Makefile2
-rw-r--r--protocols/msn/msn.c2
-rw-r--r--protocols/msn/msn.h12
-rw-r--r--protocols/msn/msn_util.c95
-rw-r--r--protocols/msn/ns.c2
-rw-r--r--protocols/msn/sb.c9
-rw-r--r--protocols/msn/soap.c256
-rw-r--r--protocols/msn/soap.h87
8 files changed, 460 insertions, 5 deletions
diff --git a/protocols/msn/Makefile b/protocols/msn/Makefile
index 781482f5..b9c7ed28 100644
--- a/protocols/msn/Makefile
+++ b/protocols/msn/Makefile
@@ -12,7 +12,7 @@ SRCDIR := $(SRCDIR)protocols/msn/
endif
# [SH] Program variables
-objects = msn.o msn_util.o ns.o passport.o sb.o tables.o
+objects = msn.o msn_util.o ns.o passport.o sb.o soap.o tables.o
LFLAGS += -r
diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c
index 60d58532..6222e1b6 100644
--- a/protocols/msn/msn.c
+++ b/protocols/msn/msn.c
@@ -102,6 +102,8 @@ static void msn_logout( struct im_connection *ic )
while( md->groupcount > 0 )
g_free( md->grouplist[--md->groupcount] );
g_free( md->grouplist );
+ g_free( md->passport_token );
+ g_free( md->lock_key );
while( md->grpq )
{
diff --git a/protocols/msn/msn.h b/protocols/msn/msn.h
index 31683cb5..f060000a 100644
--- a/protocols/msn/msn.h
+++ b/protocols/msn/msn.h
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2004 Wilmer van der Gaast and others *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
\********************************************************************/
/* MSN module */
@@ -41,6 +41,13 @@
#define QRY_NAME "msmsgs@msnmsgr.com"
#define QRY_CODE "Q1P7W2E4J9R8U3S5"
+/* This should be MSN Messenger 7.0.0813 */
+//#define MSNP11_PROD_KEY "CFHUR$52U_{VIX5T"
+//#define MSNP11_PROD_ID "PROD0101{0RM?UBW"
+
+#define MSNP11_PROD_KEY "O4BG@C7BWLYQX?5G"
+#define MSNP11_PROD_ID "PROD01065C%ZFN6F"
+
#define MSN_SB_NEW -24062002
#define MSN_MESSAGE_HEADERS "MIME-Version: 1.0\r\n" \
@@ -68,6 +75,8 @@ struct msn_data
struct msn_handler_data *handler;
int trId;
+ char *passport_token;
+ char *lock_key;
GSList *msgq, *grpq;
GSList *switchboards;
@@ -174,6 +183,7 @@ int msn_handler( struct msn_handler_data *h );
char *msn_http_encode( const char *input );
void msn_msgq_purge( struct im_connection *ic, GSList **list );
gboolean msn_set_display_name( struct im_connection *ic, const char *rawname );
+char *msn_p11_challenge( char *challenge );
/* tables.c */
const struct msn_away_state *msn_away_state_by_number( int number );
diff --git a/protocols/msn/msn_util.c b/protocols/msn/msn_util.c
index 23447403..954ee716 100644
--- a/protocols/msn/msn_util.c
+++ b/protocols/msn/msn_util.c
@@ -1,7 +1,7 @@
/********************************************************************\
* BitlBee -- An IRC to other IM-networks gateway *
* *
- * Copyright 2002-2004 Wilmer van der Gaast and others *
+ * Copyright 2002-2010 Wilmer van der Gaast and others *
\********************************************************************/
/* MSN module - Miscellaneous utilities */
@@ -25,6 +25,7 @@
#include "nogaim.h"
#include "msn.h"
+#include "md5.h"
#include <ctype.h>
int msn_write( struct im_connection *ic, char *s, int len )
@@ -443,3 +444,95 @@ gboolean msn_set_display_name( struct im_connection *ic, const char *rawname )
return msn_write( ic, buf, strlen( buf ) ) != 0;
}
+
+unsigned int little_endian( unsigned int dw )
+{
+#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN
+ return dw;
+#else
+ /* We're still not sure if this machine is big endian since the
+ constants above are not that portable. Don't swap bytes, just
+ force-compose a 32-bit little endian integer. */
+ unsigned int ret = 0, i;
+ char *dst = (char*) (&ret + 1);
+
+ for (i = 0; i < 4; i ++)
+ {
+ *(--dst) = dw >> 24;
+ dw <<= 8;
+ }
+
+ return ret;
+#endif
+}
+
+/* Copied and heavily modified from http://tmsnc.sourceforge.net/chl.c */
+char *msn_p11_challenge( char *challenge )
+{
+ char *output, buf[256];
+ md5_state_t md5c;
+ unsigned char md5Hash[16], *newHash;
+ unsigned int *md5Parts, *chlStringParts, newHashParts[5];
+ long long nHigh = 0, nLow = 0;
+ int i, n;
+
+ /* Create the MD5 hash */
+ md5_init(&md5c);
+ md5_append(&md5c, (unsigned char*) challenge, strlen(challenge));
+ md5_append(&md5c, (unsigned char*) MSNP11_PROD_KEY, strlen(MSNP11_PROD_KEY));
+ md5_finish(&md5c, md5Hash);
+
+ /* Split it into four integers */
+ md5Parts = (unsigned int *)md5Hash;
+ for (i = 0; i < 4; i ++)
+ {
+ md5Parts[i] = little_endian(md5Parts[i]);
+
+ /* & each integer with 0x7FFFFFFF */
+ /* and save one unmodified array for later */
+ newHashParts[i] = md5Parts[i];
+ md5Parts[i] &= 0x7FFFFFFF;
+ }
+
+ /* make a new string and pad with '0' */
+ n = g_snprintf(buf, sizeof(buf)-5, "%s%s00000000", challenge, MSNP11_PROD_ID);
+ /* truncate at an 8-byte boundary */
+ buf[n&=~7] = '\0';
+
+ /* split into integers */
+ chlStringParts = (unsigned int *)buf;
+
+ /* this is magic */
+ for (i = 0; i < (n / 4) - 1; i += 2)
+ {
+ long long temp;
+
+ chlStringParts[i] = little_endian(chlStringParts[i]);
+ chlStringParts[i+1] = little_endian(chlStringParts[i+1]);
+
+ temp = (md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
+ nHigh = (md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
+ nLow = nLow + nHigh + temp;
+ }
+ nHigh = (nHigh+md5Parts[1]) % 0x7FFFFFFF;
+ nLow = (nLow+md5Parts[3]) % 0x7FFFFFFF;
+
+ newHashParts[0] ^= nHigh;
+ newHashParts[1] ^= nLow;
+ newHashParts[2] ^= nHigh;
+ newHashParts[3] ^= nLow;
+
+ /* swap more bytes if big endian */
+ for (i = 0; i < 4; i ++)
+ newHashParts[i] = little_endian(newHashParts[i]);
+
+ /* make a string of the parts */
+ newHash = (unsigned char *)newHashParts;
+
+ /* convert to hexadecimal */
+ output = g_new(char, 33);
+ for (i = 0; i < 16; i ++)
+ sprintf(output + i * 2, "%02x", newHash[i]);
+
+ return output;
+}
diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c
index 0be9e727..40c4cdec 100644
--- a/protocols/msn/ns.c
+++ b/protocols/msn/ns.c
@@ -782,6 +782,8 @@ static void msn_auth_got_passport_token( struct msn_auth_data *mad )
{
char buf[1024];
+ md->passport_token = g_strdup( mad->token );
+
g_snprintf( buf, sizeof( buf ), "USR %d TWN S %s\r\n", ++md->trId, mad->token );
msn_write( ic, buf, strlen( buf ) );
}
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
index cb5789b8..10425708 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -28,7 +28,7 @@
#include "msn.h"
#include "passport.h"
#include "md5.h"
-#include "invitation.h"
+#include "soap.h"
static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition cond );
static int msn_sb_command( gpointer data, char **cmd, int num_parts );
@@ -624,7 +624,12 @@ static int msn_sb_command( gpointer data, char **cmd, int num_parts )
int num = atoi( cmd[0] );
const struct msn_status_code *err = msn_status_by_number( num );
- imcb_error( ic, "Error reported by switchboard server: %s", err->text );
+ /* If the person is offline, send an offline message instead,
+ and don't report an error. */
+ if( num == 217 )
+ msn_soap_oim_send_queue( ic, &sb->msgq );
+ else
+ imcb_error( ic, "Error reported by switchboard server: %s", err->text );
if( err->flags & STATUS_SB_FATAL )
{
diff --git a/protocols/msn/soap.c b/protocols/msn/soap.c
new file mode 100644
index 00000000..82ecfea2
--- /dev/null
+++ b/protocols/msn/soap.c
@@ -0,0 +1,256 @@
+/** soap.c
+ *
+ * SOAP-related functions. Some manager at Microsoft apparently thought
+ * MSNP wasn't XMLy enough so someone stepped up and changed that. This
+ * is the result.
+ *
+ * Copyright (C) 2010 Wilmer van der Gaast <wilmer@gaast.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that is will be useful,
+ * bit WITHOU 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "http_client.h"
+#include "soap.h"
+#include "msn.h"
+#include "bitlbee.h"
+#include "url.h"
+#include "misc.h"
+#include "base64.h"
+#include "xmltree.h"
+#include <ctype.h>
+#include <errno.h>
+
+typedef enum
+{
+ MSN_SOAP_OK,
+ MSN_SOAP_RETRY,
+ MSN_SOAP_ABORT,
+} msn_soap_result_t;
+
+struct msn_soap_req_data;
+
+typedef int (*msn_soap_func) ( struct msn_soap_req_data * );
+
+struct msn_soap_req_data
+{
+ void *data;
+ struct im_connection *ic;
+ int ttl;
+
+ char *url, *action, *payload;
+ struct http_request *http_req;
+
+ const struct xt_handler_entry *xml_parser;
+ msn_soap_func build_request, handle_response, free_data;
+};
+
+static int msn_soap_send_request( struct msn_soap_req_data *req );
+
+static int msn_soap_start( struct im_connection *ic,
+ void *data,
+ msn_soap_func build_request,
+ const struct xt_handler_entry *xml_parser,
+ msn_soap_func handle_response,
+ msn_soap_func free_data )
+{
+ struct msn_soap_req_data *req = g_new0( struct msn_soap_req_data, 1 );
+
+ req->ic = ic;
+ req->data = data;
+ req->xml_parser = xml_parser;
+ req->build_request = build_request;
+ req->handle_response = handle_response;
+ req->free_data = free_data;
+ req->ttl = 3;
+
+ return msn_soap_send_request( req );
+}
+
+static void msn_soap_handle_response( struct http_request *http_req );
+
+static int msn_soap_send_request( struct msn_soap_req_data *soap_req )
+{
+ char *http_req;
+ url_t url;
+
+ soap_req->build_request( soap_req );
+
+ url_set( &url, soap_req->url );
+ http_req = g_strdup_printf( SOAP_HTTP_REQUEST, url.file, url.host,
+ soap_req->action, strlen( soap_req->payload ), soap_req->payload );
+
+ soap_req->http_req = http_dorequest( url.host, url.port, url.proto == PROTO_HTTPS,
+ http_req, msn_soap_handle_response, soap_req );
+
+ g_free( http_req );
+
+ return soap_req->http_req != NULL;
+}
+
+static void msn_soap_handle_response( struct http_request *http_req )
+{
+ struct msn_soap_req_data *soap_req = http_req->data;
+ int st;
+
+ if( http_req->body_size > 0 )
+ {
+ struct xt_parser *parser;
+
+ parser = xt_new( soap_req->xml_parser, soap_req );
+ xt_feed( parser, http_req->reply_body, http_req->body_size );
+ xt_handle( parser, NULL, -1 );
+ xt_free( parser );
+ }
+
+ st = soap_req->handle_response( soap_req );
+
+ g_free( soap_req->url );
+ g_free( soap_req->action );
+ g_free( soap_req->payload );
+ soap_req->url = soap_req->action = soap_req->payload = NULL;
+
+ if( st == MSN_SOAP_RETRY && --soap_req->ttl )
+ msn_soap_send_request( soap_req );
+ else
+ {
+ soap_req->free_data( soap_req );
+ g_free( soap_req );
+ }
+}
+
+
+/* oim_send: Sending offline messages */
+
+struct msn_soap_oim_send_data
+{
+ char *to;
+ char *msg;
+ int number;
+ int need_retry;
+};
+
+static int msn_soap_oim_build_request( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+ char *display_name_b64;
+
+ display_name_b64 = tobase64( ic->displayname );
+
+ soap_req->url = g_strdup( SOAP_OIM_SEND_URL );
+ soap_req->action = g_strdup( SOAP_OIM_ACTION_URL );
+ soap_req->payload = g_markup_printf_escaped( SOAP_OIM_SEND_PAYLOAD,
+ ic->acc->user, display_name_b64, oim->to, md->passport_token,
+ MSNP11_PROD_ID, md->lock_key ? : "", oim->number, oim->number, oim->msg );
+
+ g_free( display_name_b64 );
+
+ return 1;
+}
+
+static xt_status msn_soap_oim_send_challenge( struct xt_node *node, gpointer data )
+{
+ struct msn_soap_req_data *soap_req = data;
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+ struct im_connection *ic = soap_req->ic;
+ struct msn_data *md = ic->proto_data;
+
+ g_free( md->lock_key );
+ md->lock_key = msn_p11_challenge( node->text );
+
+ oim->need_retry = 1;
+
+ return XT_HANDLED;
+}
+
+static const struct xt_handler_entry msn_soap_oim_send_parser[] = {
+ { "LockKeyChallenge", "detail", msn_soap_oim_send_challenge },
+ { NULL, NULL, NULL }
+};
+
+static int msn_soap_oim_handle_response( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ if( soap_req->http_req->status_code == 500 && oim->need_retry && soap_req->ttl > 0 )
+ {
+ oim->need_retry = 0;
+ return MSN_SOAP_RETRY;
+ }
+ else if( soap_req->http_req->status_code == 200 )
+ {
+ imcb_log( soap_req->ic, "Offline message successfully delivered to %s", oim->to );
+ return MSN_SOAP_OK;
+ }
+ else
+ {
+ imcb_log( soap_req->ic, "Failed to deliver offline message to %s:\n%s", oim->to, oim->msg );
+ return MSN_SOAP_ABORT;
+ }
+}
+
+static int msn_soap_oim_free_data( struct msn_soap_req_data *soap_req )
+{
+ struct msn_soap_oim_send_data *oim = soap_req->data;
+
+ g_free( oim->to );
+ g_free( oim->msg );
+ g_free( oim );
+
+ return 0;
+}
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg )
+{
+ struct msn_soap_oim_send_data *data;
+
+ data = g_new0( struct msn_soap_oim_send_data, 1 );
+ data->to = g_strdup( to );
+ data->msg = tobase64( msg );
+ data->number = 1;
+
+ return msn_soap_start( ic, data, msn_soap_oim_build_request,
+ msn_soap_oim_send_parser,
+ msn_soap_oim_handle_response,
+ msn_soap_oim_free_data );
+}
+
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq )
+{
+ GSList *l;
+ char *n = NULL;
+
+ for( l = *msgq; l; l = l->next )
+ {
+ struct msn_message *m = l->data;
+
+ if( n == NULL )
+ n = m->who;
+ if( strcmp( n, m->who ) == 0 )
+ msn_soap_oim_send( ic, m->who, m->text );
+ }
+
+ while( *msgq != NULL )
+ {
+ struct msn_message *m = (*msgq)->data;
+
+ g_free( m->who );
+ g_free( m->text );
+ g_free( m );
+
+ *msgq = g_slist_remove( *msgq, m );
+ }
+}
diff --git a/protocols/msn/soap.h b/protocols/msn/soap.h
new file mode 100644
index 00000000..3db2d59d
--- /dev/null
+++ b/protocols/msn/soap.h
@@ -0,0 +1,87 @@
+/* soap.h
+ *
+ * SOAP-related functions. Some manager at Microsoft apparently thought
+ * MSNP wasn't XMLy enough so someone stepped up and changed that. This
+ * is the result.
+ *
+ * Copyright (C) 2010 Wilmer van der Gaast <wilmer@gaast.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that is will be useful,
+ * bit WITHOU 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* Thanks to http://msnpiki.msnfanatic.com/ for lots of info on this! */
+
+#ifndef __SOAP_H__
+#define __SOAP_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#endif
+#include "nogaim.h"
+
+
+#define SOAP_HTTP_REQUEST \
+"POST %s HTTP/1.0\r\n" \
+"Host: %s\r\n" \
+"Accept: */*\r\n" \
+"SOAPAction: \"%s\"\r\n" \
+"User-Agent: BitlBee " BITLBEE_VERSION "\r\n" \
+"Content-Type: text/xml; charset=utf-8\r\n" \
+"Content-Length: %d\r\n" \
+"Cache-Control: no-cache\r\n" \
+"\r\n" \
+"%s"
+
+
+#define SOAP_OIM_SEND_URL "https://ows.messenger.msn.com/OimWS/oim.asmx"
+#define SOAP_OIM_ACTION_URL "http://messenger.msn.com/ws/2004/09/oim/Store"
+
+#define SOAP_OIM_SEND_PAYLOAD \
+"<?xml version=\"1.0\" encoding=\"utf-8\"?>" \
+"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" \
+"<soap:Header>" \
+ "<From memberName=\"%s\" friendlyName=\"=?utf-8?B?%s?=\" xml:lang=\"nl-nl\" proxy=\"MSNMSGR\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\" msnpVer=\"MSNP13\" buildVer=\"8.0.0328\"/>" \
+ "<To memberName=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Ticket passport=\"%s\" appid=\"%s\" lockkey=\"%s\" xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\"/>" \
+ "<Sequence xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/rm\">" \
+ "<Identifier xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">http://messenger.msn.com</Identifier>" \
+ "<MessageNumber>%d</MessageNumber>" \
+ "</Sequence>" \
+"</soap:Header>" \
+"<soap:Body>" \
+ "<MessageType xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">text</MessageType>" \
+ "<Content xmlns=\"http://messenger.msn.com/ws/2004/09/oim/\">" \
+ "MIME-Version: 1.0\r\n" \
+ "Content-Type: text/plain; charset=UTF-8\r\n" \
+ "Content-Transfer-Encoding: base64\r\n" \
+ "X-OIM-Message-Type: OfflineMessage\r\n" \
+ "X-OIM-Run-Id: {89527393-8723-4F4F-8005-287532973298}\r\n" \
+ "X-OIM-Sequence-Num: %d\r\n" \
+ "\r\n" \
+ "%s" \
+ "</Content>" \
+"</soap:Body>" \
+"</soap:Envelope>"
+
+int msn_soap_oim_send( struct im_connection *ic, const char *to, const char *msg );
+int msn_soap_oim_send_queue( struct im_connection *ic, GSList **msgq );
+
+#endif /* __SOAP_H__ */