/* msnp2p.cpp - msn p2p protocol Copyright (c) 2003-2004 by Olivier Goffart <[email protected]> ************************************************************************* * * * 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. * * * ************************************************************************* */ #include "msnp2p.h"
#include <stdlib.h> // qt #include <qregexp.h> #include <qfile.h> #include <qtextcodec.h> // kde #include <kdebug.h> #include <kmdcodec.h> #include <ktempfile.h> #include <krun.h> #include <klocale.h> #include <kglobal.h> #include <kdeversion.h> #include <kstandarddirs.h> //kopete #include <kopetemessagemanager.h> // { Just for getting the contact #include <kopeteaccount.h> // { #include <kopetetransfermanager.h>
MSNP2P::MSNP2P( QObject *parent , const char * name ) : QObject( parent , name ) { m_file=0l; m_Sfile=0L; m_Rfile=0L; m_msgIdentifier=0; m_sessionId=0; m_totalDataSize=0; m_offset=0; m_kopeteTransfer=0L; }
MSNP2P::~MSNP2P() { if(m_file) delete m_file; else delete m_Rfile; delete m_Sfile; } void MSNP2P::slotReadMessage( const QByteArray &msg ) { QString messageHeader=QCString(msg.data() , (msg.find('\0')==-1) ? msg.size() : msg.find('\0') );
QRegExp rx("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); rx.search( messageHeader ); QString type=rx.cap(1); if( type== "application/x-msnmsgrp2p" ) { //Get the starting position of the 48-bytes bunary header unsigned int startBinHeader=0; bool justCR=false; while(startBinHeader < msg.size()-2) { if( msg.data()[startBinHeader]=='\r') startBinHeader++; if( msg.data()[startBinHeader]=='\n' ) { if(justCR) break; else justCR=true; } else justCR=false; startBinHeader++; } startBinHeader++; if(!justCR || startBinHeader+48 > msg.size()) { //no binary header, or not long enough if(m_kopeteTransfer) { m_kopeteTransfer->slotError( KIO::ERR_INTERNAL , i18n("Malformed packet received") ); m_kopeteTransfer=0L; } abortCurrentTransfer(); return; } //Read some interesting field from the binary header unsigned int dataMessageSize=(int)(unsigned char)(msg.data()[startBinHeader+24]) + (int)((unsigned char)msg.data()[startBinHeader+25])*256; unsigned int totalSize=(int)(unsigned char)(msg.data()[startBinHeader+16]) + (int)((unsigned char)msg.data()[startBinHeader+17])*256 + (int)((unsigned char)msg.data()[startBinHeader+18])*256*256 + (int)((unsigned char)msg.data()[startBinHeader+19])*256*256*256; unsigned int dataOffset=(int)(unsigned char)(msg.data()[startBinHeader+8]) + (int)((unsigned char)msg.data()[startBinHeader+9])*256 + (int)((unsigned char)msg.data()[startBinHeader+10])*256*256 + (int)((unsigned char)msg.data()[startBinHeader+11])*256*256*256; if(dataMessageSize==0) { kdDebug(14140) << "MSNP2P::slotReadMessage: I do not care, it's a ACK - flag= " << (int)(unsigned char)(msg.data()[startBinHeader+28]) << endl; return; } if(msg.size() < startBinHeader+48+dataMessageSize) { //the message's size is shorter than the announced size if(m_kopeteTransfer) { m_kopeteTransfer->slotError( KIO::ERR_INTERNAL , i18n("Malformed packet received") ); m_kopeteTransfer=0L; } abortCurrentTransfer(); return; } QString dataMessage=QCString((msg.data()+startBinHeader+48) , dataMessageSize); if(m_msgHandle.isEmpty()) { //if these addresses were not previously set, get it, they should be provided in the first message at last. QRegExp rx("To: <msnmsgr:([^>]*)>"); if( rx.search( dataMessage ) != -1 ) m_myHandle=rx.cap(1); rx=QRegExp("From: <msnmsgr:([^>]*)>"); if( rx.search( dataMessage ) != -1 ) m_msgHandle=rx.cap(1); } //Send the ack if needed if(dataOffset+dataMessageSize>=totalSize) sendP2PAck( (msg.data()+startBinHeader) ); if(m_Rfile) //we are already downloading something to this file { //m_file->file()->writeBlock( (msg.data()+startBinHeader+48) , dataMessageSize ); m_Rfile->writeBlock( (msg.data()+startBinHeader+48) , dataMessageSize ); if(m_kopeteTransfer) m_kopeteTransfer->slotProcessed( dataOffset+dataMessageSize ); if(dataOffset+dataMessageSize >= totalSize) //the file is complete { if(m_file) { m_file->close(); emit fileReceived(m_file , m_obj); m_file=0; m_Rfile=0L; } else { if(m_kopeteTransfer) m_kopeteTransfer->slotComplete(); m_Rfile->close(); delete m_Rfile; m_Rfile=0L; } /* delete m_file;*/ //send the bye message makeMSNSLPMessage(BYE, QString::null); //deleteLater(); } } else { kdDebug(14141) << "MSNP2P::slotReadMessage: dataMessage: " << dataMessage << endl; if(msg.data()[startBinHeader+48] == '\0' ) { //This can be only the data preparaion message. prepare to download m_file=new KTempFile( locateLocal( "tmp", "msnpicture-" ), ".png" ); m_file->setAutoDelete(true); m_Rfile=m_file->file(); } else if (dataMessage.contains("INVITE")) { //Parse the message to get some info for replying QRegExp rx(";branch=\\{([0-9A-F\\-]*)\\}\r\n"); rx.search( dataMessage ); m_branch=rx.cap(1); rx=QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); rx.search( dataMessage ); m_CallID=rx.cap(1); if(!m_kopeteTransfer) // it's the first INVITE message { rx=QRegExp("SessionID: ([0-9]*)\r\n"); rx.search( dataMessage ); m_sessionId=rx.cap(1).toUInt(); rx=QRegExp("AppID: ([0-9]*)\r\n"); rx.search( dataMessage ); unsigned long int AppID=rx.cap(1).toUInt(); if(AppID==1) //the peer ask for a msn picture, or emoticon download. { // currently, we always send the display picture
//Send the OK message. QString content="SessionID: " + QString::number( m_sessionId ) + "\r\n\r\n" ; makeMSNSLPMessage( OK, content ); //prepare to send the file m_Sfile = new QFile( locateLocal( "appdata", "msnpicture-"+ m_myHandle.lower().replace(QRegExp("[./~]"),"-") +".png" ) ); if(!m_Sfile->open(IO_ReadOnly)) {/* TODO: error?*/} //send the data preparation message QByteArray initM(4); initM.fill('\0'); sendP2PMessage(initM); m_totalDataSize= m_Sfile->size(); QTimer::singleShot( 10, this, SLOT(slotSendData()) ); //Go for upload } else if(AppID==2) //the peer want to transfer a file. { //extract the context from the invitation contents rx=QRegExp("Context: ([0-9a-zA-Z+/=]*)"); rx.search( dataMessage ); QString context=rx.cap(1); //Context is a base64 encoded dump of the internal memory of MSN messanger. // the filesize is contained in the bytes 8..11 // the filename is from the byte 19 // I don't know what other fields are. QByteArray binaryContext; KCodecs::base64Decode( context.utf8() , binaryContext ); if(binaryContext.size() < 21 ) //security, (don't crash) return; //TODO: handle error //the filename is conteined in the context from the 19st char to the end. (in utf-16) QTextCodec *codec = QTextCodec::codecForName("ISO-10646-UCS-2"); if(!codec) return; //abort(); QString filename = codec->toUnicode(binaryContext.data()+19 , binaryContext.size()-19-16) ; filename=filename.left(filename.find(QChar('\0'))); //the size is placed in the context in the bytes 8..12 (source: the amsn code) unsigned long int filesize= (unsigned char)(binaryContext[8]) + (unsigned char)(binaryContext[9]) *256 + (unsigned char)(binaryContext[10]) *65536 + (unsigned char)(binaryContext[11]) *16777216 ; //ugly hack to get the KopeteContact. KopeteContact *c=0L; if(parent()) { KopeteMessageManager *kmm=dynamic_cast<KopeteMessageManager*>(parent()->parent()); if(kmm) c=kmm->account()->contacts()[m_msgHandle]; } disconnect(KopeteTransferManager::transferManager(), 0L , this, 0L); connect(KopeteTransferManager::transferManager() , SIGNAL(accepted(KopeteTransfer*, const QString& )) , this, SLOT(slotTransferAccepted(KopeteTransfer*, const QString& ))); connect(KopeteTransferManager::transferManager() , SIGNAL(refused( const KopeteFileTransferInfo & ) ), this, SLOT( slotFileTransferRefused( const KopeteFileTransferInfo & ) ) ); //show a dialog to ask the transfer. KopeteTransferManager::transferManager()->askIncomingTransfer(c , filename , filesize, QString::null, QString::number(m_sessionId)+":"+m_branch+":"+m_CallID); } else //unknwon AppID { makeMSNSLPMessage( ERROR, QString::null ); } } // end of if(m_kopeteTranfer) else // we are nogitiating a complex invitaiton ( a file transfer) { // it's the second INVITE message //dirrect connection is not yet implemented, use the connection via MSNP2P QString content="Bridge: TCPv1\r\n" "Listening: false\r\n" "Nonce: {00000000-0000-0000-0000-000000000000}\r\n\r\n"; makeMSNSLPMessage(OK, content); m_Rfile=new QFile( m_kopeteTransfer->destinationURL().path() ); if(!m_Rfile->open(IO_WriteOnly)) { if(m_kopeteTransfer) { //TODO: handle the QFILE error m_kopeteTransfer->slotError( KIO::ERR_CANNOT_OPEN_FOR_WRITING , i18n("Cannot open file for writing") ); m_kopeteTransfer=0L; return; } abortCurrentTransfer(); } } } else if (dataMessage.contains("BYE")) { //deleteLater(); } } } else { kdDebug(14140) << "MSNSwitchBoardSocket::slotReadMessage: Unknown type '" << type << endl; } } void MSNP2P::requestDisplayPicture( const QString &myHandle, const QString &msgHandle, QString msnObject) { //reset some field /* m_file=0l; m_Sfile=0L; m_msgIdentifier=0; m_sessionId=0; m_totalDataSize=0; m_offset=0;*/ m_sessionId=0; m_myHandle=myHandle; m_msgHandle=msgHandle; m_obj=msnObject; msnObject=QString::fromUtf8(KCodecs::base64Encode( msnObject.utf8() )); msnObject.replace("=" , QString::null ) ; unsigned long int sessID=rand()%0xFFFFFF00+4; m_branch= QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number(rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16)+QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16)+QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16); m_CallID= QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + QString::number(rand()%0xAAFF+0x1111, 16) + "-" + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16)+QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16)+QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16); ; QString content="EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n" "SessionID: "+ QString::number(sessID)+"\r\n" "AppID: 1\r\n" "Context: " + msnObject +"\r\n\r\n"; makeMSNSLPMessage( INVITE , content ); } void MSNP2P::makeMSNSLPMessage( MessageType type, QString content ) { QString contentType= QString( "application/x-msnmsgr-sessionreqbody" ); QString method; QString CSeq; switch(type) { case INVITE: method="INVITE MSNMSGR:"+ m_msgHandle + " MSNSLP/1.0"; CSeq="0"; break; case DECLINE: method="MSNSLP/1.0 603 DECLINE"; CSeq="1"; break; case ERROR: contentType="null"; method="MSNSLP/1.0 500 Internal Error"; CSeq="1"; break; case OK: if(m_kopeteTransfer) contentType="application/x-msnmsgr-transreqbody"; method="MSNSLP/1.0 200 OK"; CSeq="1"; break; case BYE: contentType="application/x-msnmsgr-sessionclosebody"; method="BYE MSNMSGR:"+m_msgHandle+" MSNSLP/1.0"; CSeq="0"; break; }
QCString dataMessage= QString( method + "\r\n" "To: <msnmsgr:"+m_msgHandle+">\r\n" "From: <msnmsgr:"+m_myHandle+">\r\n" "Via: MSNSLP/1.0/TLP ;branch={"+m_branch.upper()+"}\r\n" "CSeq: "+ CSeq +"\r\n" "Call-ID: {"+m_CallID.upper()+"}\r\n" "Max-Forwards: 0\r\n" "Content-Type: "+ contentType +"\r\n" "Content-Length: "+ QString::number(content.length()+1)+"\r\n" "\r\n" + content ).utf8(); //\0 //the data message must be end by \0, bye chance, QCString automaticaly appends \0 at the end of the QByteArray kdDebug(14141) << k_funcinfo << dataMessage << endl; sendP2PMessage(dataMessage); } void MSNP2P::sendP2PMessage(const QByteArray &dataMessage) { bool transferStarted=( m_Rfile || m_Sfile ); //we are stransfering binary is any of the file exists QCString messageHeader=QString( "MIME-Version: 1.0\r\n" "Content-Type: application/x-msnmsgrp2p\r\n" "P2P-Dest: "+ m_msgHandle +"\r\n\r\n").utf8();
QByteArray binHeader(48); binHeader.fill('\0'); //fill with 0 for starting
if(m_msgIdentifier==0) m_msgIdentifier=rand()%0x0FFFFFF0+4; else if(m_offset==0) m_msgIdentifier++; //SessionID unsigned long int sessionID=transferStarted ? m_sessionId : 0; binHeader[0]=(char)(sessionID%256); binHeader[1]=(char)((unsigned long int)(sessionID/256)%256); binHeader[2]=(char)((unsigned long int)(sessionID/(256*256))%256); binHeader[3]=(char)((unsigned long int)(sessionID/(256*256*256))%256); //MessageID binHeader[4]=(char)(m_msgIdentifier%256); binHeader[5]=(char)((unsigned long int)(m_msgIdentifier/256)%256); binHeader[6]=(char)((unsigned long int)(m_msgIdentifier/(256*256))%256); binHeader[7]=(char)((unsigned long int)(m_msgIdentifier/(256*256*256))%256); //offset binHeader[8]=(char)(m_offset%256); binHeader[9]=(char)((unsigned long int)(m_offset/256)%256); binHeader[10]=(char)((unsigned long int)(m_offset/(256*256))%256); binHeader[11]=(char)((unsigned long int)(m_offset/(256*256*256))%256); unsigned int size=dataMessage.size(); if(m_totalDataSize) //it's a splitted message { binHeader[16]=(char)(m_totalDataSize%256); binHeader[17]=(char)((unsigned long int)(m_totalDataSize/256)%256); binHeader[18]=(char)((unsigned long int)(m_totalDataSize/(256*256))%256); binHeader[19]=(char)((unsigned long int)(m_totalDataSize/(256*256*256))%256); //update offset m_offset+=size; if(m_offset>=m_totalDataSize) { //message completely sent, reset values m_offset=0; m_totalDataSize=0; } } else //not a splitted message, the total size is the current size { binHeader[16]=(char)size%256; binHeader[17]=(int)size/256; } //message size binHeader[24]=(char)size%256; binHeader[25]=(int)size/256; //Ack sessionID binHeader[32]=(char)(rand()%256); binHeader[33]=(char)(rand()%256); binHeader[34]=(char)(rand()%256); binHeader[35]=(char)(rand()%256); /*binHeader[32]=0xDE; binHeader[33]=0xC7; binHeader[34]=0x07; binHeader[35]=0x14;*/ //merge all in a unique message QByteArray data( messageHeader.length() + binHeader.size() + dataMessage.size() + 4 ); for(unsigned int f=0; f< messageHeader.length() ; f++) data[f]=messageHeader[f]; for(unsigned int f=0; f< binHeader.size() ; f++) data[messageHeader.length()+f]=binHeader[f]; for(unsigned int f=0; f< dataMessage.size() ; f++) data[messageHeader.length()+binHeader.size()+f]=dataMessage[f]; for(unsigned int f=0; f< 4 ; f++) //footer data[messageHeader.length()+binHeader.size()+dataMessage.size()+f]='\0';
if(transferStarted) { //then, the footer ends with \1 (only for display pictures) data[messageHeader.length()+binHeader.size() + dataMessage.size() +3 ]='\1'; } //send the message emit sendCommand("MSG", "D" , true , data , true ); } void MSNP2P::sendP2PAck( const char* originalHeader ) { QCString messageHeader=QString( "MIME-Version: 1.0\r\n" "Content-Type: application/x-msnmsgrp2p\r\n" "P2P-Dest: "+ m_msgHandle +"\r\n\r\n").utf8(); QByteArray binHeader(48); binHeader.fill('\0'); //fill with 0 for starting
//sessionID binHeader[0]=originalHeader[0]; binHeader[1]=originalHeader[1]; binHeader[2]=originalHeader[2]; binHeader[3]=originalHeader[3]; //MessageID bool a=false; if(m_msgIdentifier==0) { m_msgIdentifier=rand()%0xFFFFFE00+10; a=true; } else m_msgIdentifier++; binHeader[4]=(char)(m_msgIdentifier%256); binHeader[5]=(char)((unsigned long int)(m_msgIdentifier/256)%256); binHeader[6]=(char)((unsigned long int)(m_msgIdentifier/(256*256))%256); binHeader[7]=(char)((unsigned long int)(m_msgIdentifier/(256*256*256))%256); if(a) m_msgIdentifier-=4; //total size binHeader[16]=originalHeader[16]; binHeader[17]=originalHeader[17]; binHeader[18]=originalHeader[18]; binHeader[19]=originalHeader[19]; binHeader[20]=originalHeader[20]; binHeader[21]=originalHeader[21]; binHeader[22]=originalHeader[22]; binHeader[23]=originalHeader[23]; //flag binHeader[28]=(char)0x02; //ack sessionID binHeader[32]=originalHeader[4]; binHeader[33]=originalHeader[5]; binHeader[34]=originalHeader[6]; binHeader[35]=originalHeader[7]; //ack unique id binHeader[36]=originalHeader[32]; binHeader[37]=originalHeader[33]; binHeader[38]=originalHeader[34]; binHeader[39]=originalHeader[35]; //ack data size binHeader[40]=originalHeader[16]; binHeader[41]=originalHeader[17]; binHeader[42]=originalHeader[18]; binHeader[43]=originalHeader[19]; binHeader[44]=originalHeader[20]; binHeader[45]=originalHeader[21]; binHeader[46]=originalHeader[22]; binHeader[47]=originalHeader[23]; QByteArray data( messageHeader.length() + binHeader.size() + 4 ); for(unsigned int f=0; f< messageHeader.length() ; f++) data[f]=messageHeader[f]; for(unsigned int f=0; f< binHeader.size() ; f++) //if binHeader is a QCString, it ends with \0 , which is ok data[messageHeader.length()+f]=binHeader[f]; for(unsigned int f=0; f< 4 ; f++) data[messageHeader.length()+binHeader.size() +f ]='\0';
emit sendCommand("MSG", "D" , true , data , true ); } void MSNP2P::slotSendData() { if(!m_Sfile) return;
char data[1200]; int bytesRead = m_Sfile->readBlock( data,1200 ); QByteArray dataBA(bytesRead); for ( int f = 0; f < bytesRead; f++ ) dataBA[f] = data[f]; // kdDebug(14140) << "MSNP2P::slotSendData: offset=" << m_offset << " size=" << bytesRead << " totalSize=" << m_totalDataSize << " sent=" << m_offset+bytesRead << endl; sendP2PMessage(dataBA); if( m_totalDataSize == 0 ) //this has been reseted bacause the file is completely send { // kd(14140) << "MSNP2P::slotSendData: FINISHED! wait for the BYE message" << endl; delete m_Sfile; m_Sfile=0L; m_sessionId=0; } else QTimer::singleShot( 10, this, SLOT(slotSendData()) ); } void MSNP2P::slotTransferAccepted(KopeteTransfer* transfer, const QString& /*filename*/ ) { QStringList internalId=QStringList::split(":" , transfer->info().internalId() ); if(internalId[0].toUInt() == m_sessionId ) { QObject::connect(transfer , SIGNAL(transferCanceled()), this, SLOT(abortCurrentTransfer())); QObject::connect(transfer, SIGNAL(destroyed()) , this , SLOT(slotKopeteTransferDestroyed())); m_branch=internalId[1]; QString callid=internalId[2]; QString content="SessionID: " + QString::number( m_sessionId ) +"\r\n\r\n"; makeMSNSLPMessage( OK, content); m_kopeteTransfer=transfer; } } void MSNP2P::slotFileTransferRefused( const KopeteFileTransferInfo &info ) { QStringList internalId=QStringList::split(":" , info.internalId() ); kdDebug(14140) << k_funcinfo << internalId << endl; if(internalId[0].toUInt() == m_sessionId ) { m_branch=internalId[1]; m_CallID=internalId[2]; QString content="SessionID: " + QString::number( m_sessionId ) +"\r\n\r\n"; makeMSNSLPMessage( DECLINE , content ); } } void MSNP2P::abortCurrentTransfer() { if(m_kopeteTransfer) { delete m_Rfile; m_Rfile=0L; //this need to be reseted before sending the BYE message. m_totalDataSize=0; m_offset=0;
//FIXME: i'm not sure it's like that i should abort the transfer. makeMSNSLPMessage(BYE, "\r\n\r\n" ); m_sessionId=0; m_msgIdentifier=0; } } void MSNP2P::slotKopeteTransferDestroyed() { m_kopeteTransfer=0L; kdDebug(14140) << k_funcinfo << endl; }
#include "msnp2p.moc" 
|