/*************************************************************************** msnnotificationconnection.cpp - description ------------------- begin : Thu Jan 23 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.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. * * * ***************************************************************************/ #include "msnnotificationconnection.h" #include "../contact/contact.h" #include "../model/contactlist.h" #include "../utils/kmessshared.h" #include "../currentaccount.h" #include "../kmess.h" #include "../kmessapplication.h" #include "../kmessdebug.h" #include "../kmessinterface.h" #include "soap/addressbookservice.h" #include "soap/passportloginservice.h" #include "soap/offlineimservice.h" #include "soap/roamingservice.h" #include "utils/kmessconfig.h" #include "chatmessage.h" #include "mimemessage.h" #include "msnchallengehandler.h" #include <QTextDocument> // for Qt::escape() #include <QUrl> #include <KDialog> #include <KLocale> #include <KMessageBox> #ifdef KMESSDEBUG_CONTACTLISTMODELTEST #include "../../tests/modeltest/modeltest.h" #endif #ifdef KMESSDEBUG_NOTIFICATION #define KMESSDEBUG_NOTIFICATION_GENERAL // #define KMESSDEBUG_NOTIFICATION_AUTH // #define KMESSDEBUG_NOTIFICATION_MESSAGES #endif // Settings for debugging. #define NOTIFICATION_HIDE_P2P_SUPPORT 0 /** * Maximum allowed time between unique server responses * * KMess will wait at most this number of milliseconds after receiving a server command * (any command, in fact). If nothing arrives and this timeout expires, the login process * or the connection will be aborted. */ #define NOTIFICATION_COMMAND_INTERVAL_TIMEOUT 30000 /** * @brief The constructor * * Initializes the default groups of the ContactList class. */ 00071 MsnNotificationConnection::MsnNotificationConnection() : MsnConnection( MsnSocketBase::SERVER_NOTIFICATION ), addressBookService_(0), connected_(false), currentAccount_(0), enableNotifications_(false), initialized_(false), isInitiatingConnection_(false), offlineImService_(0), passportLoginService_(0) { setObjectName( "MsnNotificationConnection" ); // We manage the contact list contactList_ = new ContactList(); #ifdef KMESSDEBUG_CONTACTLISTMODELTEST // Start the Contact List model tests new ModelTest( contactList_ ); #endif // Periodically close expired switchboard requests connect( this, SIGNAL( pingSent() ), this, SLOT ( checkSwitchboardsTimeout() ) ); // Attach a timer to watch over the login process loginTimer_.stop(); loginTimer_.setSingleShot( true ); connect( &loginTimer_, SIGNAL( timeout() ), this, SLOT ( slotAuthenticationFailed() ) ); } /** * @brief The destructor * * Deletes the contact list. * The base class closes the connection. */ 00111 MsnNotificationConnection::~MsnNotificationConnection() { delete contactList_; // Remove all open requests. qDeleteAll( openRequests_ ); openRequests_.clear(); // Remove all offline im messages qDeleteAll( offlineImMessages_ ); offlineImMessages_.clear(); // closeConnection() also cleans up a lot too. #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "DESTROYED."; #endif } /** * @brief Add a contact to another group. * * It's possible to add a contact to multiple groups, * typically referred to as "copy contact to group..". * * @param handle The contact handle. * @param groupId The group ID. */ 00141 void MsnNotificationConnection::addContactToGroup(QString handle, QString groupId) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "adding '" << handle << " to '" << groupId << "'."; #endif AddressBookService *addressBook = createAddressBookService(); Contact *contact = contactList_->getContactByHandle( handle ); if( contact == 0 ) { return; } addressBook->addContactToGroup( contact->getGuid(), groupId ); } /** * @brief Re-add a known contact to the friends list * * The contact will no longer appear in a "allowed contacts" * or "removed contacts" group, but appear at the friends list again. * The contact should exist already at some list * before (e.g. the block list or friends list). * * * @param handle Email address of the contact. * @param groupsId Groups where to add the contact. */ 00171 void MsnNotificationConnection::addExistingContact( QString handle, const QStringList& groupsId ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Re-adding contact " << handle; #endif // Request the re-adding of contact to AB AddressBookService *addressBook = createAddressBookService(); addressBook->addContact( handle, groupsId , true ); } /** * @brief Add a new contact to the list. * * This is the standard method to add a contact. Optionally, it can also add it to a specific group. * * @param handle Email address of the contact. * @param groupId groupId of the group where to place the new contact. */ 00192 void MsnNotificationConnection::addNewContact( QString handle, const QStringList& groupsId ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Adding contact " << handle; #endif // Request the adding of contact to AB AddressBookService *addressBook = createAddressBookService(); addressBook->addContact( handle, groupsId ); } /** * @brief Adds the given contact to the allow list only. * * It allows the contact to see the online state of the user. * The contact will not appear in the main window * until it's also added to the friends list using addExistingContact(). * * This method can only be called when the contact exists * in the reverse list only. This happens when the contact is new to the user, * or it was completely removed. Call unblockContact() instead when the * contact needs to be unblocked. * * This method is used when a new contact appears: * - to add the contact to the friends list too, call addNewContact() * - to allow the contact only, call this method. * - to block the contact, call blockContact() * * Internally, this method runs the required putAdc() calls to change the contact lists: * - the contact is added to the allow list (<code>AL</code>). * * @param handle Email address of the contact. */ 00227 void MsnNotificationConnection::allowContact(QString handle) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Allowing contact " << handle; #endif putAdl( handle, Contact::MSN_LIST_ALLOWED ); AddressBookService *addressBook = createAddressBookService(); addressBook->unblockContact( handle ); } /** * @brief Add a new group * * This sends an <code>ADG</code> command to the server. * When gotAdg() receives the server response, the ContactList object is updated. * * @param name Name of the group. */ 00249 void MsnNotificationConnection::addGroup(QString name) { AddressBookService *addressBook = createAddressBookService(); addressBook->addGroup( name ); } /** * @brief Block the given contact * * When the contact also exists in the friends list, * it will remain there with a blocked status icon. * When the contact only exists in the "allowed contacts" group, * it moves to the "removed contacts" group with a blocked status icon. * * This method can also be used when a new contact appears: * - to add the contact to the friends list too, call addNewContact() * - to allow the contact only, call allowContact() * - to block the contact, call this method. * * Internally, this method runs the required putAdc() calls to change the contact lists: * - the contact is added to the block list (<code>BL</code>). * * @param handle Email address of the contact. */ 00275 void MsnNotificationConnection::blockContact(QString handle) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Blocking contact " << handle; #endif putRml( handle, Contact::MSN_LIST_ALLOWED ); putAdl( handle, Contact::MSN_LIST_BLOCKED ); AddressBookService *addressBook = createAddressBookService(); addressBook->blockContact( handle ); } /** * @brief Change the current media of the user. * * This is used to exchange the "now playing" information. * It sends an <code>UUX</code> command to the server. * More information about this command can be found * at: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#UUX * * @param application Name of the media application, typically empty. iTunes and Winamp are known to be accepted. * @param mediaType Type of the media, either "Music", "Games" or "Office". * @param enabled Whether the media setting is enabled or not. * @param formatString A .Net style formatting string, e.g. "{0} - {1}". * @param arguments Replacements for the placeholders in the formatStrirng. */ 00304 void MsnNotificationConnection::changeCurrentMedia( const QString &application, const QString &mediaType, bool enabled, const QString &formatString, const QStringList arguments ) { // Update current media string QString media( application + "\\0" + mediaType + "\\0" + ( enabled ? "1" : "0" ) + "\\0" + formatString + "\\0" + arguments.join( "\\0" ) ); lastCurrentMedia_ = Qt::escape( media ); // Send personal status and current media putUux(); } /** * @brief Called when the MSN Object of the current user changed. * * When this method is called, contacts will be informed the user has a new display picture. * Some clients automatically initiate a background chat to retrieve the new display picture. * Other clients wait until a real chat is initiated, to request the display picture at that moment. * * Internally, this method calls changeStatus( Status ) because * that same command is used to notify about MsnObject changes. * The MsnObject is extracted from the CurrentAccount class. */ 00330 void MsnNotificationConnection::changedMsnObject() { if(isConnected()) { // Send the same status message, with a new msn object changeStatus(currentAccount_->getStatus()); } } /** * @brief Change the friendly name of the user. * * Internally, this method uses changeProperty() to set the <code>MFN</code> property. * In MSNP15 the command PRP must be send with SOAP request to avoid nickname will disappear * the next time we sign in. * * @param newName The new friendly name. */ 00350 void MsnNotificationConnection::changeFriendlyName( QString newName ) { if( ! currentAccount_->isVerified() ) { return; } currentAccount_->setFriendlyName( newName ); changeProperty("MFN", newName); AddressBookService *addressBook = createAddressBookService(); addressBook->contactUpdate( AddressBookService::PROPERTY_FRIENDLYNAME, newName ); } // Change the personal properties void MsnNotificationConnection::changePersonalProperties( const QString& friendlyname, const QString& cid, int blp ) { // Send BLP command for privacy settings, please refer to: // http://msnpiki.msnfanatic.com/index.php/Command:BLP const QString& blpToSend( ( blp == 0 ) ? "BL" : "AL" ); sendCommand( "BLP", blpToSend ); changeFriendlyName( friendlyname ); // Check if necessary to retrieve information ( like pm, picture ) from msn storage // using roaming service // TODO complete the support Q_UNUSED( cid ); /* RoamingService *roamingService = new RoamingService( this ); roamingService->getProfile( cid ); */ } /** * @brief Change the personal message of the user. * * The personal mesage typically contains a short status message * displayed next to the contact. * * This method sends the <code>UUX</code> payload command to the server. * * @param newMessage The new status message. */ 00399 void MsnNotificationConnection::changePersonalMessage( QString newMessage ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Changing personal message to " << newMessage; #endif lastPsm_ = Qt::escape(newMessage); putUux(); } /** * @brief Change a user property * * It sends the <code>PRP</code> (likely short for property) command to the server. * * Valid property types are: * - <code>MFN</code>: MSN Friendly name * - <code>PHH</code>: Phone home * - <code>PHW</code>: Phone work * - <code>PHM</code>: Phone mobile * - <code>MOB</code>: MSN Mobile authorised for others: Y or N * - <code>MBE</code>: MSN Mobile enabled * - <code>WWE</code>: MSN direct? * - <code>HSB</code>: Has blog: <code>1</code> or <code>0</code> * * This function is also used internally by other methods like changeFriendlyName() * * @param type The property type. * @param value The new property value, can also be empty. */ 00431 void MsnNotificationConnection::changeProperty( QString type, QString value ) { if( value.isEmpty() ) { sendCommand( "PRP", type ); } else { value = QUrl::toPercentEncoding( value ); // To avoid problem with a very long nick value.truncate( 387 ); sendCommand( "PRP", type + " " + value ); } } /** * @brief Change a contact property * * It sends the <code>SBP</code> (likely short for set-buddy-property) command to the server. * * @param handle Email address of the contact. * @param type The property type, e.g. <code>MFN</code>, see the other changeProperty() method. * @param value The new property value, can also be empty. */ 00459 void MsnNotificationConnection::changeProperty( QString handle, QString type, QString value ) { // Get contact const Contact *contact = contactList_->getContactByHandle(handle); if(KMESS_NULL(contact)) return; // Avoid disconnects by the server if( contact->getGuid().isEmpty() ) { kWarning() << "Can't change property, contact GUID is empty" << " (contact=" << handle << " property=" << type << ")." << endl; return; } // First guess is that "SBP" means set-buddy-property if( value.isEmpty() ) { sendCommand( "SBP", contact->getGuid() + " " + type ); } else { value = QUrl::toPercentEncoding( value ); // To avoid problem with a very long nick value.truncate( 387 ); sendCommand( "SBP", contact->getGuid() + " " + type + " " + value ); } } /** * @brief Change the status of the user. * * This sends the <code>CHG</code> command to the user. * This command also informs contacts the user's MsnObject and client capabilities. * The MsnObject contains the meta-data about the user's display picture. * The display picture itself is transferred in a chat session. * * Note this method advertises the supported client features, like 'winks' and 'webcam'. * * @param newStatus The new user status. * @todo Possibly rename this to putChg() instead, especially when webcam support makes the capabilities dynamic. */ 00504 void MsnNotificationConnection::changeStatus( const Status newStatus ) { #if NOTIFICATION_HIDE_P2P_SUPPORT const uint capabilities = 0; #else // NOTE: When changing this property, all MSNP2P code need to be retested again!! // The capabilities are like a contract. The other client assumes everything works that's promised here. #if ENABLE_WEBCAM // FIXME: only show webcam capability if the user has a webcam. const uint capabilities = ( Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET | Contact::MSN_CAP_INK_GIF | Contact::MSN_CAP_VIDEO_CHAT ); #else const uint capabilities = ( Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET | Contact::MSN_CAP_INK_GIF ); #endif #endif const QString& statusCode( MsnStatus::getCode( newStatus ) ); const QString objStr( QUrl::toPercentEncoding( currentAccount_->getMsnObjectString() ) ); /* // To test the old file transfer: sendCommand( "CHG", newStatus + " 0"); return; */ if( objStr.isEmpty() ) { // Just don't send it if we don't have it. sendCommand( "CHG", statusCode + " " + QString::number(capabilities) ); } else { sendCommand( "CHG", statusCode + " " + QString::number(capabilities) + " " + objStr ); } } // Removes any expired switchboard requests, so new ones can be made void MsnNotificationConnection::checkSwitchboardsTimeout() { // There are no pending switchboard requests, bail out if( openRequests_.count() < 1 ) { return; } #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Removing expired switchboard requests."; #endif // Delete any pending switchboard uint now = QDateTime::currentDateTime().toTime_t(); foreach( ChatInformation *switchboardInfo, openRequests_ ) { if( ( now - switchboardInfo->getTime() ) > 60 ) { openRequests_.removeAll( switchboardInfo ); delete switchboardInfo; } } } /** * @brief Close the connection with the server * * This disconnects from the server. It also cleans up the * state variables and objects associated with the session, * e.g. contactlist data, open chat invitations, pending offline-im messages and SOAP clients. * * The ContactList object is saved and reset as well. */ 00577 void MsnNotificationConnection::closeConnection() { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Close the connection."; #endif // Disconnect from the server first. // emits the signals so other classes can clean up / save properties. disconnectFromServer(); // Stop the login timer loginTimer_.stop(); // Delete all SOAP clients, and unset local references here. deleteSoapClients(); offlineImService_ = 0; passportLoginService_ = 0; isInitiatingConnection_ = false; // Remove contacts and non-special groups from the contact list if ( contactList_ != 0 ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Resetting contactlist."; #endif // Save the contact list settings saveProperties(); // The contact list is not deleted, like this class it's being // shared through the entire application, and attached to slots. contactList_->reset( false ); } // Clear all pending offline messages pendingOfflineImMessages_.clear(); // Delete all offline im messages qDeleteAll( offlineImMessages_ ); offlineImMessages_.clear(); // Delete all open requets qDeleteAll( openRequests_ ); openRequests_.clear(); // Reset current account // TODO: use different connection for this? if( currentAccount_ != 0 ) { currentAccount_->setNoEmails( 0 ); } } /** * @brief Create and return one pointer to address book service. * */ 00637 AddressBookService* MsnNotificationConnection::createAddressBookService() { if( addressBookService_ == 0 ) { addressBookService_ = new AddressBookService( this ); addSoapClient( addressBookService_ ); // Contact actions signals/slots connect( addressBookService_, SIGNAL( contactAdded( const QString&, const QString&, const QStringList& ) ), this, SLOT( slotContactAdded( const QString&, const QString&, const QStringList& ) ) ); connect( addressBookService_, SIGNAL( contactDeleted( const QString& ) ), this, SLOT( slotContactDeleted( const QString& ) ) ); connect( addressBookService_, SIGNAL( contactBlocked( const QString& ) ), this, SLOT( slotContactBlocked( const QString& ) ) ); connect( addressBookService_, SIGNAL( contactUnblocked( const QString& ) ), this, SLOT( slotContactUnblocked( const QString& ) ) ); // Contact added/remove to/from group signals/slots connect( addressBookService_, SIGNAL( contactAddedToGroup( const QString&, const QString& ) ), this, SLOT( slotContactAddedToGroup( const QString&, const QString& ) ) ); connect( addressBookService_, SIGNAL( contactDeletedFromGroup( const QString&, const QString& ) ), this, SLOT( slotContactDeletedFromGroup( const QString&, const QString& ) ) ); // Group actions signals/slots connect( addressBookService_, SIGNAL( groupAdded( QString, QString ) ), contactList_, SLOT( addGroup( QString, QString ) ) ); connect( addressBookService_, SIGNAL( groupDeleted( QString ) ), contactList_, SLOT( removeGroup( QString ) ) ); connect( addressBookService_, SIGNAL( groupRenamed( QString, QString ) ), contactList_, SLOT( renameGroup( QString, QString ) ) ); // Membership and AddressBook signals/slots connect( addressBookService_, SIGNAL( gotMembershipLists(const QString&,const QHash<QString,int>&) ), this, SLOT( slotGotMembershipLists(const QString&,const QHash<QString,int>&) ) ); connect( addressBookService_, SIGNAL( gotAddressBookList( const QList< QHash<QString,QVariant> >& ) ), this, SLOT( slotGotAddressBookList( const QList< QHash<QString,QVariant> >& ))); connect( addressBookService_, SIGNAL( gotGroup( const QString&, const QString& ) ), this, SLOT( slotGotGroup( const QString&, const QString& ) ) ); connect( addressBookService_, SIGNAL( gotPersonalInformation(const QString&,const QString&,int) ), this, SLOT( changePersonalProperties(const QString&,const QString&,int) ) ); connect( addressBookService_, SIGNAL( soapWarning(const QString&,bool) ), this, SLOT ( slotWarning(const QString&,bool) ) ); } return addressBookService_; } /** * @brief Internal function to create the Offline-IM service * * This service is used to retrieve the Offline-IM messages. * The signals are attached to receivedOfflineIm() and receivedMailData() * * @returns The Offline-IM webservice handler */ 00697 OfflineImService* MsnNotificationConnection::createOfflineImService() { // Extract the 't' and 'p' values from the current authentication ticket string // This is done beacuse sendMessage() could has requested one new ticket const QStringList& fields( currentAccount_->getToken( "Messenger" ).split("&") ); QString authT, authP; foreach( const QString &field, fields ) { if( field.startsWith("t=") ) { authT = field.mid(2); } else if( field.startsWith("p=") ) { authP = field.mid(2); } #ifdef KMESSDEBUG_NOTIFICATION else { kWarning() << "Could not parse authentication field:" << field; } #endif } // Request the first offline-im message // TODO reuse the pointer, and set the token like addressbook OfflineImService *oimService = new OfflineImService( authT, authP, this ); // Add the soap client. addSoapClient( oimService ); // Register signals connect( oimService, SIGNAL( messageReceived( const QString&, const QString&, const QString&, const QDateTime&, const QString&, const QString&, int ) ), this, SLOT ( receivedOfflineIm( const QString&, const QString&, const QString&, const QDateTime&, const QString&, const QString&, int ) )); connect( oimService, SIGNAL( metaDataReceived( QDomElement ) ), this, SLOT ( receivedMailData( QDomElement ) )); return oimService; } /** * @brief Internal function to create the passport login handler. * * This service is used by gotUsr() to retrieve the passport authentication token. * * @returns The Passport login webservice handler */ 00749 PassportLoginService* MsnNotificationConnection::createPassportLoginService() { // TODO reuse the pointer // Create the login handler. PassportLoginService *loginService = new PassportLoginService(); // Add the soap client. addSoapClient( loginService ); // Connect all login notifications connect( loginService, SIGNAL( loginIncorrect() ), this, SLOT ( loginIncorrect() ) ); connect( loginService, SIGNAL( loginSucceeded() ), this, SLOT ( loginSucceeded() ) ); return loginService; } /** * @brief Compute a triple des encryption. * * Used to compute the value of string to send to server for SSO. * * @param key The key for compute the encryption * @param secret The secret data to encryption * @param iv The initial vector for cbc mode */ 00778 QByteArray MsnNotificationConnection::createTripleDes ( const QByteArray key, const QByteArray secret, const QCA::InitializationVector& iv ) { // check for Triple Des with CBC support. if ( ! QCA::isSupported( "tripledes-cbc" ) ) { kWarning() << "Triple DES encryption not supported, please install qca2 packages"; return ""; } // Create the key using the key parameter and secret data QCA::SymmetricKey desKey( key ); QCA::SecureArray data( secret ); // Create the TripleDes object. QCA::Cipher des3( QString ( "tripledes" ), QCA::Cipher::CBC, QCA::Cipher::NoPadding, QCA::Encode, desKey, iv ); // Chiper the data and check for the errors QCA::SecureArray temp = des3.update ( data ); if ( ! des3.ok() ) { kWarning() << "Triple DES computation failed on update step"; return ""; } // Finish the compute and check for errors des3.final(); if ( ! des3.ok() ) { kWarning() << "Triple DES computation failed on final step"; return ""; } // Return the encrypted data return temp.toByteArray(); } /** * @brief Return the contact list model * * @returns The model from the contact list managed by this class. */ 00822 QAbstractItemModel* MsnNotificationConnection::getContactListModel() { return contactList_; } /** * @brief Called when the user is ready to go online. * * This method is called by gotSyn() or gotLst() when * the entire contact list data is received. * It changes some final things before the user goes online: * - set the initial user status, e.g. "online" or "invisible". * - ses the initial personal message. * - requests other server data, like Hotmail email URL's. * Finally, the connected() signal is fired. */ 00840 void MsnNotificationConnection::goOnline() { int ack; if(KMESS_NULL(currentAccount_)) return; // Stop the authentication timer, we're done with that part loginTimer_.stop(); // Start the ping timer: from now on, it's its duty to verify that the connection is still active setSendPings( true ); // Load the contact list settings readProperties(); // Change to the initial status #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Changing initial status to" << MsnStatus::getCode( currentAccount_->getInitialStatus() ); #endif changeStatus( currentAccount_->getInitialStatus() ); // Reset status fields. lastCurrentMedia_ = QString::null; lastPsm_ = QString::null; // Start with default personal message (may be empty) changePersonalMessage( currentAccount_->getPersonalMessage( STRING_ORIGINAL ) ); // When the account supports email (e.g. Hotmail), request the URL's. // This didn't give problems with non-email accounts, // but restricted accounts return a "710" error for this command. if( currentAccount_->getEmailSupported() ) { // The ACK is temporary stored as inbox-command, // so the response can be mapped back to the requested folder. QHash<QString,QString>& hash = currentAccount_->getUrlInformation(); // Ask for inbox URL ack = sendCommand("URL", "INBOX"); hash.insert( QString::number(ack), "INBOX" ); // Ask for compose URL ack = sendCommand("URL", "COMPOSE"); hash.insert( QString::number(ack), "COMPOSE" ); // Ask the personal profile URL ack = sendCommand("URL", "PROFILE"); hash.insert( QString::number(ack), "PROFILE" ); // Ask the URL for change account info ack = sendCommand("URL", "PERSON"); hash.insert( QString::number(ack), "PERSON" ); } // Notify observers that the server is connected emit connected(); // Start a timer: for the first three seconds after connecting, do not display any notifications. // See unlockNotifications() for more details QTimer::singleShot( 3000, this, SLOT( unlockNotifications() ) ); } // Received response to adl command void MsnNotificationConnection::gotAdl( const QStringList& command, const QString& payload ) { // The server responds to a successful ADL command if( command[2] == "OK" ) { // Check if already connect if( ! connected_ ) { // Go online goOnline(); emit connected(); connected_ = true; } return; } if( ! payload.isEmpty() ) { QRegExp regexp( ".*n=\"([^\"]+)\".*n=\"([^\"]+)\".*l=\"([^\"]+)\"" ); int pos = regexp.indexIn( payload ); int lists = -1; QString handle; if( pos != -1 ) { handle = regexp.cap(2) + "@" + regexp.cap(1); lists = QVariant( regexp.cap(3) ).toInt(); } if( lists == -1 || handle.isEmpty() ) { return; } Contact *contact = contactList_->getContactByHandle( handle ); if( contact != 0 ) { if( lists & Contact::MSN_LIST_FRIEND ) contact->setFriend ( true ); if( lists & Contact::MSN_LIST_ALLOWED ) contact->setAllowed( true ); if( lists & Contact::MSN_LIST_BLOCKED ) contact->setBlocked( true ); if( lists & Contact::MSN_LIST_REVERSE ) contact->setReverse( true ); if( lists & Contact::MSN_LIST_PENDING ) contact->setPending( true ); } else { // Else if the contact isn't present, ask to user what he want to do. kDebug() << "The contact: " << handle << " not present in contact list added user"; emit contactAddedUser( handle ); } } } // Received confirmation of the user's status change void MsnNotificationConnection::gotChg( const QStringList& command ) { if(KMESS_NULL(currentAccount_)) return; #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "User's status changed to " << command[2] << "."; #endif // Change the user's status currentAccount_->setStatus( MsnStatus::codeToStatus( command[2] ) ); } // Received a challenge from the server void MsnNotificationConnection::gotChl( const QStringList& command ) { // Handle challenge query MSNChallengeHandler handler; const QString& response( handler.computeHash( command[2] ) ); // Send response sendPayloadMessage( "QRY", handler.getProductId(), response ); } // Received a version update from the server void MsnNotificationConnection::gotCvr( const QStringList& command ) { Q_UNUSED( command ); if(KMESS_NULL(currentAccount_)) return; // Set the connected status to false connected_ = false; // Send the USR command sendCommand( "USR", "SSO I " + currentAccount_->getHandle() ); } // Received notice that a contact went offline void MsnNotificationConnection::gotFln( const QStringList& command ) { if(KMESS_NULL(contactList_)) return; #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << command[1] << " went offline."; #endif contactList_->changeContactStatus( command[1].toLower(), STATUS_OFFLINE ); } // Received notice that a contact is already online void MsnNotificationConnection::gotIln( const QStringList& command ) { if(KMESS_NULL(contactList_)) return; // Get the contact info from the message const QString& handle ( command[3].toLower() ); const QString& friendlyName ( QUrl::fromPercentEncoding( command[5].toUtf8() ) ); QString msnObject ( QUrl::fromPercentEncoding( command[7].toUtf8() ) ); uint capabilities = command[6].toUInt(); Status status = MsnStatus::codeToStatus( command[2] ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << handle << " is initially " << command[2] << "."; #endif // Check if the received MSN object is empty, a "0" (Kopete), or shorter than the string "<msnobject/>" if( msnObject.isEmpty() || msnObject.compare( QString( "0" ) ) <= 0 || msnObject.length() < 12 ) { msnObject = QString::null; } #ifdef KMESSDEBUG_NOTIFICATION_GENERAL if( ! msnObject.isEmpty() ) { kDebug() << "This person has an msn6 picture: " << msnObject; } #endif contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, false ); } /** * @brief Handle the <code>NLN</code> command; a contact changed it's status. * * When a user updates it's status with the <code>CHG</code> command, * all it's contact receive a <code>NLN</code> command. * KMess sends the <code>CHG</code> command with the changeStatus() method. * The <code>CHG</code> allows a contact to change it's name, client capabilities or MsnObject. * * The capabilities are a bitwise flag. The list of known values * are described in the ContactBase::MsnClientCapabilities enum. * * The MsnObject is an identifier for the display picture. * To download the actual picture, a client needs to initiate a chat, * and send an invitation there. * * @code <<< NLN NLN user@kmessdemo.org KMess%20Demo 1342210080 %3Cmsnobj%20Creator%3D%22user%40kmessdemo.org%22%20Size%3D%2211581%22%20Type%3D%223%22%20Location%3D%22KMess.tmp%22%20Friendly%3D%22AA%3D%3D%22%20SHA1D%3D%223VfOMCTTkuYHDjJhPx4sSlPiM%2Bs%3D%22%20SHA1C%3D%22ULeT2WGsdabLGNP3RgbWYw7ve80%3D%22/%3E @endcode * * @param command The command arguments. * - <code>command[1]</code> is the status code, see changeStatus() * - <code>command[2]</code> is the contact handle. * - <code>command[3]</code> networkid. * - <code>command[4]</code> is the contact name, url encoded. * - <code>command[5]</code> is the client-capabilities flag. * - <code>command[6]</code> is the url-encoded MsnObject XML code. */ 01079 void MsnNotificationConnection::gotNln( const QStringList& command ) { if(KMESS_NULL(contactList_)) return; // Get the contact info from the message const QString& handle ( command[2].toLower() ); const QString& friendlyName ( QUrl::fromPercentEncoding( command[4].toUtf8() ) ); QString msnObject ( QUrl::fromPercentEncoding( command[6].toUtf8() ) ); Status status = MsnStatus::codeToStatus( command[1] ); uint capabilities = command[5].toUInt(); // For some reason an empty MSNObject is sent as "0" with MSNP12 if( msnObject == "0" ) { msnObject = QString::null; } #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << handle << " is now " << MsnStatus::getCode( status ) << "."; #endif // Update contact status. contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, enableNotifications_ ); } // Received notice of disconnection from the server void MsnNotificationConnection::gotOut( const QStringList& command ) { // If the first command parameter is "OTH", it means that this account has connected from elsewhere. if( command[1] == "OTH" ) { slotError( "Connected from another location", MsnSocketBase::ERROR_CONNECTION_OTHER ); // Don't try reconnecting when connected from elsewhere. } else { closeConnection(); // Try connecting again emit reconnect( currentAccount_->getHandle() ); } } // Received a property change (friendly name, phone number, etc..) void MsnNotificationConnection::gotPrp( const QStringList& command ) { const QString& propertyName ( command[1] ); const QString& propertyValue( QUrl::fromPercentEncoding( command[2].toUtf8() ) ); if( propertyName == "MFN" ) { currentAccount_->setFriendlyName( propertyValue ); } else if( propertyName == "MBE" ) { // Unimplemented. // indicates whether MSN Mobile is enabled. } else if( propertyName == "WWE" ) { // Unimplemented // indicates whether MSN Direct is enabled. } else { // kWarning() << "Received unsupported property name/value: " << propertyName << " " << propertyValue << "!"; } } void MsnNotificationConnection::gotRml( const QStringList &command ) { // The server responds to a successful RML command if( command[2] == "OK" ) { } } /** * @brief Received a connection request from a contact * * Format at MSNP15: * RNG 1295917726 65.54.228.15:1863 CKI 104194101.9520122 email@domain.com Friendly%20Name U messenger.msn.com 1\r\n */ 01173 void MsnNotificationConnection::gotRng( const QStringList& command ) { // Pull the data from the message const QString& chatId ( command[1] ); const QString& serverAndPort( command[2] ); const QString& auth ( command[4] ); const QString& handle ( command[5].toLower() ); const QString& server ( serverAndPort.section(':', 0, 0) ); const QString& portString( serverAndPort.section(':', 1, 1) ); bool goodPort; quint16 port = (quint16) portString.toUInt( &goodPort ); if( ! goodPort ) { kWarning() << "Couldn't get port from string " << portString; return; } #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << handle << " is requesting a chat at: " << server << ":" << port << " id=" << chatId << " auth=" << auth; #endif // Send connection information object emit startSwitchboard( ChatInformation( this, handle, server, port, auth, chatId, ChatInformation::CONNECTION_BACKGROUND ) ); } // Received information about a contact's personal message void MsnNotificationConnection::gotUbx( const QStringList &command, const QByteArray &payload ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Got UBX information."; #endif Contact *contact = contactList_->getContactByHandle( command[1].toLower() ); if(KMESS_NULL(contact)) return; if( command[3].toInt() == 0 ) { // Size is 0, no personal message contact->setPersonalStatus( QString::null ); } else { // Load XML parser QDomDocument xml; if( ! xml.setContent(payload) ) { kWarning() << "Could not parse personal status message (invalid XML, contact=" << command[1] << ")!"; kWarning() << "The invalid XML was:" << payload; } // Parse payload const QDomElement& root( xml.namedItem("Data").toElement() ); const QString& psm( root.namedItem("PSM").toElement().text() ); QString currentMedia( root.namedItem("CurrentMedia").toElement().text() ); QString mediaType; // Append current media if set. if( currentMedia.length() > 0 ) { // From http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes: // // Media string can be something like: // <CurrentMedia>\0Music\01\0{0} - {1}\0Title\0Artist\0Album\0\0</CurrentMedia> // <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia> // <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia> // // The literal "\0" is the separator char. // Fields are: application, mediatype, enabled, formatstring, arg1, arg2, arg3 // Split media string const QStringList media( currentMedia.split( "\\0" ) ); mediaType = media[1]; // Check if media is enabled, to find out what this means. if( media[2] != "1" ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "CurrentMedia is set, but not enabled."; #endif currentMedia = QString::null; } else { // Replace fields in .Net style format string currentMedia = media[3]; // format for( int i = 4; i < media.count(); i++ ) { currentMedia = currentMedia.replace( "{" + QString::number(i - 4) + "}", media[i] ); } } } // Store personal message contact->setPersonalStatus( psm, mediaType, currentMedia ); } } // Received the folder and command info for Hotmail's inbox or compose // The response from ns is like to: // URL 68 /cgi-bin/compose https://login.live.com/ppsecure/md5auth.srf?lc=1040 2 // 68 is an ack number, /cgi-bin/compose is the ru ( url command ) for the specify service // and the last url is the site to send the query. Note: KMess use sha1auth instead md5 // so the store of this last url is useless! void MsnNotificationConnection::gotUrl( const QStringList& command ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Got URL information."; #endif if(KMESS_NULL(currentAccount_)) return; // Grep the hash where store the informations QHash<QString,QString>& hash( currentAccount_->getUrlInformation() ); // Check if in the hash there is already the ack request // Remove the item with the key "ack" and value "SERVICE" and switch the element // so the key will be the "SERVICE" and the value will be the URL. const QString& service( hash.take( command[1] ) ); if( ! service.isEmpty() ) { hash.insert( service, command[2] ); } } /** * @brief Received a USR command from the server. * * USR command for the login. At first we must send the handle with wich we want to log on. * When the server response to first USR, we take the nonce value used for compute the return value. * The key used for encrypt the data ( 3DES ) must be taken from Passport SOAP request. * The computed value ( encryption of nonce with secret key taken by SOAP ) must be sended to server. * */ 01314 void MsnNotificationConnection::gotUsr( const QStringList& command ) { if(KMESS_NULL(currentAccount_)) return; const QString& step( command[2] ); // This is either asking for authentication or confirming it if( step == "SSO" ) { // The initial connection process (network connection establish and version exchange) is over. // The MSN servers sometimes fail to warn the clients that something's wrong by their side and // they close the connection before the USR. This is a nice workaround: if the connection gets // closed before this point, we just try again with HTTP, which usually works. isInitiatingConnection_ = false; const QString& policy ( command[4] ); // Policy nonceBase64_ = command[5]; // Base64 nonce // Change statusbar emit statusMessage( i18n("Authenticating..."), false ); // Get the right password value QString password; if( ! currentAccount_->getTemporaryPassword().isEmpty() ) { // Login from InitialView. Try to login with the newly entered password password = currentAccount_->getTemporaryPassword(); } else { // Login from menu. Use the current password. password = currentAccount_->getPassword(); } // Start the passport based login passportLoginService_ = createPassportLoginService(); passportLoginService_->login( policy, currentAccount_->getHandle(), password ); } else if( step == "OK" ) { // This is a confirmation message. Get the user's friendly name from the message. bool isVerified = ( command[4].toInt() == 1 ); currentAccount_->setVerified( isVerified ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL if( ! isVerified ) { kWarning() << "Passport account of the user is not verified yet."; } #endif // When users choose an existing account in the InitialView dialog, // they may alter the password. This value is stored as temporary password. // The password may be overwritten now, because the login was succesful. currentAccount_->saveTemporaryPassword(); emit statusMessage( i18n( "Authenticated" ), false ); // Retrieve from with one SOAP request the membership list ( where there are the lists for // FL AL BL contacts ) AddressBookService *addressBook = createAddressBookService(); addressBook->retrieveMembershipLists(); } else { kWarning() << "Received unexpected USR step:" << step; } } // Received version information void MsnNotificationConnection::gotVer( const QStringList& command ) { Q_UNUSED( command ); // Avoid compiler warning if(KMESS_NULL(currentAccount_)) return; // Send some fake info about the current version // First parameter is the locale-id (0x0409 is U.S. English). sendCommand( "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0324 msmsgs " + currentAccount_->getHandle() ); } /** * @brief Received server transfer information * * Format at MSNP15: * XFR 32 SB 65.54.171.31:1863 CKI 1743299383.52212212.219110167 U messenger.msn.com 1\r\n */ 01405 void MsnNotificationConnection::gotXfr(const QStringList& command) { // Get id of transaction int transactionId = command[1].toInt(); // Get informations about the server from that const QString& serverType ( command[2] ); const QString& serverAndPort ( command[3] ); const QString& server ( serverAndPort.section( ':', 0, 0 ) ); const QString& portString( serverAndPort.section( ':', 1, 1 ) ); // Convert the port to an integer bool goodPort; quint16 port = (quint16) portString.toUInt( &goodPort ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Got " << serverType << " transfer to " << server << ":" << port << "."; #endif if( ! goodPort ) { kDebug() << "Got bad port number : " << portString << "."; return; } if( serverType == "NS" ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Disconnecting from old server."; #endif // Disconnect from the existing server. disconnectFromServer( true ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Connecting to new server."; #endif // This is a notification server transfer. Switch this socket to the given server. emit statusMessage( i18n("Switching to another server..."), false ); #ifdef KMESS_NETWORK_WINDOW KMESS_NET_INIT( this, "NS " + server ); #endif // Stop the login timer: it'll be restarted while connecting to the new server loginTimer_.stop(); // Set the connection as initiating, connectToServer() is from the parent class // and can't do it itself isInitiatingConnection_ = true; // Connect to the new server. connectToServer( server, port ); } else if( serverType == "SB" ) { #ifdef KMESSTEST KMESS_ASSERT( openRequests_.count() > 0 ); int noChatsBefore = openRequests_.count(); #endif // This is a switchboard server. This message should be in response to // a user-requested chat. Look for the pending chat information. ChatInformation *switchboardInfo = 0; foreach( ChatInformation *chatInformation, openRequests_ ) { if( chatInformation->getTransactionId() == transactionId ) { switchboardInfo = chatInformation; break; } } if( switchboardInfo == 0 ) { kWarning() << "Got an XFR response, but there are no corresponding open requests."; return; } // Update the server information switchboardInfo->setServerInformation( server, port, command[5] ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "emitting startSwitchboard() signal."; #endif // Signal the new connection emit startSwitchboard( *switchboardInfo ); // Remove the first item from the active requests list openRequests_.removeAll( switchboardInfo ); delete switchboardInfo; #ifdef KMESSTEST KMESS_ASSERT( openRequests_.count() == ( noChatsBefore - 1 ) ); #endif } } /** * @brief Send the ADL command. * * ADL: 'Add List' command, uses XML to identify each contact, and works as a payload message. * Each ADL command may contain up to 150 contacts (a payload of roughly 7500 bytes). * */ 01509 void MsnNotificationConnection::putAdl( const QString &handle, int list ) { // If handle is present then the adl isn't initial ADL command, so use command for add someone into one list if( ! handle.isEmpty() ) { const QStringList& splittedHandle( handle.split( "@" ) ); const QString payload( "<ml>" "<d n=\"" + splittedHandle[1] + "\">" "<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />" "</d>" "</ml>" ); sendPayloadMessage( "ADL", QString::null, payload.arg( QString::number( list ) ) ); return; } // Use the current contact list to make the string to send with initial ADL command const QHash< QString, Contact* >& contactList( contactList_->getContactList() ); // If the list is empty, send an empty ADL if( contactList.isEmpty() ) { sendPayloadMessage( "ADL", QString(), QByteArray( "<ml l=\"1\"></ml>" ) ); return; } QHashIterator< QString,Contact* > i( contactList ); const Contact *currentContact = 0; QString adlList; QString currentAdl; bool send = false; int userType = 1; while( i.hasNext() ) { i.next(); currentContact = i.value(); if( currentContact == 0 ) { continue; } list = 0; if( currentContact->isFriend() ) { list |= Contact::MSN_LIST_FRIEND; } // The contact may be or blocked or allowed, if it is both the ADL command fails if( currentContact->isBlocked() ) { list |= Contact::MSN_LIST_BLOCKED; } else if( currentContact->isAllowed() ) { list |= Contact::MSN_LIST_ALLOWED; } // Add to ADL command only user in at least one list if( list != 0 ) { // Split the domain from handle const QStringList& splittedHandle( currentContact->getHandle().split( "@" ) ); // Set the user type userType = 1; if( currentContact->getInformation( "isMessenger3" ).toBool() ) { userType = 2; } currentAdl = "<d n=\"" + splittedHandle[1] + "\"><c n=\"" + splittedHandle[0] + "\" l=\"" + QString::number( list ) + "\" t=\"" + QString::number( userType ) + "\" /></d>"; // Check if the buffer is too large ( 7500 bytes maximum ) if( currentAdl.toLatin1().size() + adlList.toLatin1().size() >= 7450 ) { // Switch to true the send variable and go back to previous element send = true; i.previous(); } else { adlList += currentAdl; } } else { // The user should decide in which list add it emit contactAddedUser( currentContact->getHandle() ); } // Send the buffer if the send variable is true or the current contact is the last of the list if( send || ! i.hasNext() ) { sendPayloadMessage( "ADL", QString::null, "<ml l=\"1\">" + adlList + "</ml>" ); adlList = ""; send = false; } } } // Send the RML command void MsnNotificationConnection::putRml( const QString &handle, int list ) { const QStringList& splittedHandle( handle.split( "@" ) ); const QString payload( "<ml>" "<d n=\"" + splittedHandle[1] + "\">" "<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />" "</d>" "</ml>" ); sendPayloadMessage( "RML", QString::null, payload.arg( QString::number( list ) ) ); } // Initialize the object 01634 bool MsnNotificationConnection::initialize() { if ( initialized_ ) { kDebug() << "already initialized!"; return false; } if ( ! MsnConnection::initialize() ) { kDebug() << "Couldn't initialize base class."; return false; } // Determine which payload commands can be received by the Notification Connection QStringList payloadCommands; payloadCommands << "MSG" // Server message << "UBX" // contact personal status message << "UBN" << "GCF" // server config file << "NOT" // msn alerts, msn calendar << "IPG" // contact page << "ADL" ; setAcceptedPayloadCommands( payloadCommands ); // Pass the current contactlist reference to the currentaccount // so other classes can access contacts from a central point. currentAccount_ = CurrentAccount::instance(); currentAccount_->setContactList( contactList_ ); // Connect the activation signal to send notification events connect( NotificationManager::instance(), SIGNAL( eventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ), this, SLOT ( slotErrorEventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ) ); initialized_ = true; return initialized_; } /** * @brief The passport login succeeded. * * When this method gets called, CurrentAccount will contain all * the needed passport security tokens. */ 01681 void MsnNotificationConnection::loginSucceeded() { // Delete the login handler // Delay deletion so it can complete the method. deleteSoapClient( passportLoginService_ ); passportLoginService_ = 0; // Initialize the QCA and create the IV key for triple des encryption QCA::Initializer init; QCA::InitializationVector iv(8); // Create the keys and hashes needed QByteArray key1, key2, key3; QByteArray hash, des3hash; // Use the nonce received from last USR commands and the proof key for authentication QByteArray nonce = nonceBase64_.toLatin1(); // Compute the value to send with USR SSO command QString magic1( "WS-SecureConversationSESSION KEY HASH" ); QString magic2( "WS-SecureConversationSESSION KEY ENCRYPTION" ); key1 = QByteArray::fromBase64( currentAccount_->getToken( "MessengerClearProof" ).toLatin1() ); key2 = KMessShared::deriveKey( key1, magic1.toLatin1() ); key3 = KMessShared::deriveKey( key1, magic2.toLatin1() ); hash = KMessShared::createHMACSha1( key2, nonce ); // The windows api always pads using \x08 so we pad 8 bytes. nonce += QByteArray ( 8, '\x08' ); // Compute the 3des encryption des3hash = createTripleDes ( key3, nonce, iv ); if( des3hash.isEmpty() ) { return; } // Create the required 128 byte padded struct MSNPMSNG blob; blob.headerSize = 28; blob.cryptMode = 1; // CRYPT_MODE_CBC blob.cipherType = 0x6603; // Triple DES blob.hashType = 0x8004; // SHA-1 blob.ivLength = 8; blob.hashLength = 20; // Length of the HMAC hash blob.cipherLength = 72; // Copy the IV Bytes, the hash and chiper value in blob struct memcpy( reinterpret_cast<char*> (blob.ivBytes), iv.toByteArray () , des3hash.size() ); memcpy( reinterpret_cast<char*> (blob.hashBytes), hash, hash.size() ); memcpy( reinterpret_cast<char*> (blob.cipherBytes), des3hash, des3hash.size() ); // Create the byte arrays and send for login QByteArray blobs ( reinterpret_cast<const char*> ( &blob ), 128 ); sendCommand( "USR", "SSO S " + currentAccount_->getToken( "MessengerClear" ) + " " + blobs.toBase64() ); } // The passport login failed, username/password was incorrect void MsnNotificationConnection::loginIncorrect() { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Passport login was incorrect."; #endif // NOTE: a similar thing also happens in soapRequestFailed(), // but for fatal SOAP errors. slotError( "Authentication failed. Wrong login credentials.", MsnSocketBase::ERROR_AUTH_LOGIN ); } // Move the contact to another group. void MsnNotificationConnection::moveContact(QString handle, QString fromGroupId, QString toGroupId) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Moving '" << handle << "' from '" << fromGroupId << "' to '" << toGroupId << "'."; #endif AddressBookService *addressBook = createAddressBookService(); Contact *contact = contactList_->getContactByHandle( handle ); if( contact == 0 ) { return; } addressBook->addContactToGroup( contact->getGuid(), toGroupId ); // Check if the "from group" is empty or not exists. if( fromGroupId == "0" || fromGroupId.isEmpty() ) { return; } addressBook->deleteContactFromGroup( contact->getGuid(), fromGroupId ); } // Open a connection to the server bool MsnNotificationConnection::openConnection() { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Opening connection."; #endif if ( !initialized_ ) { kWarning() << "Notification should be initialized before attempting to connect."; return false; } // If the last connection was using HTTP, try again using TCP first. switchToTcpSocket(); // Initialize the contact list contactList_->reset( true ); #ifdef KMESS_NETWORK_WINDOW KMESS_NET_INIT(this, "NS messenger.hotmail.com"); #endif emit statusMessage( i18n( "Connecting..." ), false ); // Set the flag to avoid an MSN server problem. See gotUsr() isInitiatingConnection_ = true; // Attempt to connect to the default MSN server connectToServer(); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Socket started connecting to the server. Started timer."; #endif return true; } // Parse a regular command 01822 void MsnNotificationConnection::parseCommand( const QStringList& command ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Parsing command '" << command[0] << "'."; #endif // Allow some time until the next message is received if( loginTimer_.isActive() ) { loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT ); } // Find out what kind of command we've received. Numbers are errors, the others server commands. bool isNumber = false; command[0].toInt( &isNumber ); // If it's not a command, something has went wrong. if( isNumber ) { kWarning() << "Invalid command received:" << command; return; } // Identify and handle the command if( command[0] == "ADL" ) { gotAdl( command ); } else if ( command[0] == "BLP" ) { // Unimplemented. This is a privacy setting. (buddy-list-policy?) } else if ( command[0] == "BPR" ) { // Unimplemented. This contains buddy properties. } else if ( command[0] == "CHG" ) { gotChg( command ); // Status change } else if ( command[0] == "CHL" ) { gotChl( command ); // Server-side ping, aka 'challenge' } else if ( command[0] == "CVR" ) { gotCvr( command ); // Client version info } else if ( command[0] == "FLN" ) { gotFln( command ); // Contact offline } else if ( command[0] == "ILN" ) { gotIln( command ); // Contact initially online } else if ( command[0] == "NLN" ) { gotNln( command ); // Contact now online } else if ( command[0] == "OUT" ) { gotOut( command ); // Disconnection from server } else if ( command[0] == "PRP" ) { gotPrp( command ); // Contact property change } else if ( command[0] == "QRY" ) { // Ignored. Indicates the challenge was succesful, contains no parameters. } else if ( command[0] == "RNG" ) { gotRng( command ); // Chat or connection requests } else if( command[0] == "RML" ) { gotRml( command ); } else if( command[0] == "RFS" ) { // Receveid the request for refresh list // so the client reply with adl command putAdl(); } else if( command[0] == "UBN" ) { // For the moment is useless // Require more investigate } else if ( command[0] == "SBP" ) { // Unimplemented. } else if ( command[0] == "SBS" ) { // Ignored. The meaning of this command is unknown, but it's related to the // user's mobile credits (MSN Mobile). // See: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS for details } else if ( command[0] == "URL" ) { gotUrl( command ); // Hotmail folder info } else if ( command[0] == "USR" ) { gotUsr( command ); // User authentication information } else if ( command[0] == "UUX" ) { // Ignored. It's an echo back from our UUX command. Unlike other commands, // the UI is updated for this command already before the server confirms. } else if ( command[0] == "VER" ) { gotVer( command ); // Version information } else if ( command[0] == "XFR" ) { gotXfr( command ); // Server transfer data } else { // No need to bugger the common user with strange error messages kWarning() << "Unknown command" << command[0] << "received from the server! Full string:" << command.join( " " ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL slotError( i18n( "Unknown command received from the server: %1", command[0] ), MsnSocketBase::ERROR_INTERNAL ); #endif } } // Parse an error command 01957 void MsnNotificationConnection::parseError( const QStringList& command, const QByteArray &payloadData ) { // See if the error was from an open switchboard request. if( ! openRequests_.isEmpty() ) { int transactionId = command[1].toInt(); foreach( ChatInformation *chatInvite, openRequests_ ) { if( chatInvite->getTransactionId() == transactionId ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Removing chat invitation from list."; #endif openRequests_.removeAll( chatInvite ); delete chatInvite; break; } } } // Relay the error detection to the base class MsnConnection::parseError( command, payloadData ); } // Parse a message command 01985 void MsnNotificationConnection::parseMimeMessage( const QStringList& command, const MimeMessage &mainMessage ) { Q_UNUSED( command ); #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Got " << mainMessage.getSubValue("Content-Type") << "."; #endif const QString& contentType( mainMessage.getSubValue("Content-Type") ); const MimeMessage& message( mainMessage.getBody() ); #ifdef KMESSDEBUG_NOTIFICATION_MESSAGES mainMessage.print(); kDebug() << "Message body as mime:"; message.print(); #endif // Check the message type if ( contentType == "text/x-msmsgsprofile" ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Load profile information."; #endif uint externalPort = mainMessage.getValue("ClientPort").toUInt(); externalPort = ((externalPort & 0x00ff) << 8) | ((externalPort & 0xff00) >> 8); // Byte swapped currentAccount_->setAccountInformation( mainMessage.getValue("MSPAuth"), mainMessage.getValue("preferredEmail"), mainMessage.getValue("sid"), mainMessage.getValue("lang_preference"), mainMessage.getValue("EmailEnabled") == "1", mainMessage.getValue("ClientIP"), externalPort, getLocalIp() ); } else if ( contentType == "text/x-msmsgsinitialmdatanotification" || contentType == "text/x-msmsgsinitialemailnotification" || contentType == "text/x-msmsgsoimnotification" ) { // Parse both messages the same way, // the x-msmsgsinitialmdatanotification message is an extended version of x-msmsgsoimnotification // description of the fields: http://msnpiki.msnfanatic.com/index.php/MSNP13:Offline_IM // Parse the URL fields (msmsgsinitialmdatanotification messages). if( message.hasField("Post-URL") ) { currentAccount_->setEmailUrl( message.getValue("Post-URL") ); } // Parse the Mail-Data field. const QString& mailDataSrc( message.getValue("Mail-Data") ); if( mailDataSrc == "too-large" ) { // After a certain ammount of messages, the XML is replaced with "too-large". // Use SOAP to request the actual mail data. if( offlineImService_ == 0 ) { offlineImService_ = createOfflineImService(); } offlineImService_->getMetaData(); } else { // Mail-Data was received directly in the message. QDomDocument mailData; if( ! mailData.setContent(message.getValue("Mail-Data")) ) { kWarning() << "Could not parse XML Mail-Data field!"; return; } receivedMailData( mailData.namedItem("MD").toElement() ); } } else if ( contentType == "text/x-msmsgsemailnotification" ) { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "An email was received" << " from \"" << message.getValue("From") << "\"" << " with the subject \"" << message.getValue("Subject") << "\"" << " in the folder \"" << message.getValue("Dest-Folder") << "\"." << endl; #endif // If a message delta is to the active folder, emails were moved into it, so use a negative deletion if ( message.getValue("Dest-Folder") == "ACTIVE" ) { // Received in the inbox currentAccount_->changeNoEmails( 1 ); } emit newEmail( message.decodeRFC2047String( message.getValue("From").toUtf8() ), message.decodeRFC2047String( message.getValue("Subject").toUtf8() ), (message.getValue("Dest-Folder") == "ACTIVE"), message.getValue("id"), message.getValue("Message-URL"), message.getValue("Post-URL") ); } else if ( contentType == "text/x-msmsgsactivemailnotification" ) { int messageDelta = message.getValue("Message-Delta").toInt(); // If a message delta is from the active folder, emails were deleted if ( message.getValue("Src-Folder") == "ACTIVE" ) { currentAccount_->changeNoEmails( -1 * messageDelta ); } // If a message delta is to the active folder, emails were moved into it else if ( message.getValue("Dest-Folder") == "ACTIVE" ) { currentAccount_->changeNoEmails( messageDelta ); } // When deleting Offline-IM messages, this message contains: // Src-Folder: .!!OIM // Dest-Folder: .!!trAsH // Message-Delta: <number of messages deleted> } else if( contentType == "application/x-msmsgssystemmessage" ) { // This is a maintenance info message from the server. if( message.getValue("Type") == "1" ) { int timeLeft = message.getValue("Arg1").toInt(); const QString& textTimeLeft( i18ncp( "Time left before server maintenance", "%1 minute", "%1 minutes", timeLeft ) ); if( timeLeft > 2 ) { emit statusMessage( i18n("Server closes for maintenance in %1!", textTimeLeft ), false ); } else { slotError( i18nc( "Server maintenance dialog box text", "The MSN Server will be going down in %1 for maintenance.", textTimeLeft ), MsnSocketBase::ERROR_SERVER ); } } } else { kWarning() << "Content-Type '" << contentType << "' not reconized, message ignored."; } } // Parse a payload command 02131 void MsnNotificationConnection::parsePayloadMessage( const QStringList &command, const QByteArray &payload ) { if( command[0] == "UBX" ) { gotUbx( command, payload ); } else if( command[0] == "GCF" ) { // Ignore this // Contains policy information, requesting clients // to disable certain features or possibly block // certain filenames/contents. #ifdef KMESSDEBUG_NOTIFICATION_MESSAGES kDebug() << "Ignoring security configuration file, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() ); #endif } else if( command[0] == "NOT" ) { // Ignore "notification" messages coming from MSN Calendar, MSN Alerts, and MSN Mobile. #ifdef KMESSDEBUG_NOTIFICATION_MESSAGES kDebug() << "Ignoring text event notification, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() ); #endif } else if( command[0] == "ADL" ) { gotAdl( command, payload ); } else { kWarning() << "Unhandled payload command: " << command[0] << "!" << " (message dump follows)\n" << QString::fromUtf8(payload.data(), payload.size()) << endl; } } /** * @brief Send the personal status and current media fields. * * MSNP13 uses an extra field on the UUX command * */ 02173 void MsnNotificationConnection::putUux() { // TODO Calculate a GUID for the specific machine, or (if possible) find out how // WLM obtains the GUID and replicate the algorithm (to have GUIDs compatible with // WLM). If that's not needed, add the machine name (WLM knows it). const QString xml( "<Data>" "<PSM>" + lastPsm_ + "</PSM>" "<CurrentMedia>" + lastCurrentMedia_ + "</CurrentMedia>" "<MachineGuid>{F26D1F07-95E2-403C-BC18-D4BFED493428}</MachineGuid>" "</Data>" ); sendPayloadMessage( "UUX", QString::null, xml ); } /** * @brief Send the version command. * * Send versions of protocol supported by the client * */ 02196 void MsnNotificationConnection::putVer() { // Indicate which protocol version we support. // Most of the time it seems that: // - even versions are for minor changes. // - odd version is are for new revisions. sendCommand( "VER", "MSNP15 MSNP14 MSNP13 CVR0" ); // Reset the addressBookService_ because when the connection is closed the // function deleteSoap in MsnConnection delete the soap class. addressBookService_ = 0; } // Received the Mail-Data field over SOAP or at the notification connection. void MsnNotificationConnection::receivedMailData( QDomElement mailData ) { #ifdef KMESSTEST KMESS_ASSERT( mailData.tagName() == "MD" ); #endif QDomNodeList childNodes = mailData.childNodes(); for( int i = 0; i < childNodes.count(); i++ ) { QDomElement childNode = childNodes.item(i).toElement(); QString childName( childNode.nodeName() ); if( childName == "E" ) { // Found an initial email status notification. #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Load initial email information."; #endif QDomElement inboxUnread = childNode.namedItem("IU").toElement(); QDomElement othersUnread = childNode.namedItem("OU").toElement(); if( inboxUnread.isNull() || othersUnread.isNull() ) { kWarning() << "Reveived email notification, but 'IU' and 'OU' elements are missing!"; } #ifdef KMESSTEST KMESS_ASSERT( ! inboxUnread.isNull() ); KMESS_ASSERT( ! othersUnread.isNull() ); #endif currentAccount_->setInitialEmailInformation( inboxUnread.text().toInt(), othersUnread.text().toInt() ); } else if( childName == "M" ) { // Using this for-loop, multiple 'M' elements are supported. // Extract the message ID. QString messageId( childNode.namedItem("I").toElement().text() ); if( messageId.isEmpty() ) { kWarning() << "Unable to parse Mail-Data payload, offline-im message-id not found."; return; } #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Received offline-IM notification '" << messageId << "'"; #endif // Create a SOAP client to request the Offline-IM pendingOfflineImMessages_.append(messageId); } else if( childName == "Q" ) { // Not implemented } else { // Warn for unsupported tags. kWarning() << "Unsupported tag '" << childName << "' encountered in Mail-Data payload!"; } } // Start a new client if there are pending messages, and no OfflineImService is active if( ! pendingOfflineImMessages_.isEmpty() ) { // Initialize on demand if( offlineImService_ == 0 ) { offlineImService_ = createOfflineImService(); } // Request the first offline-im message if the current class was // not processing already. (otherwise receivedOfflineIm() picks the next one). if( offlineImService_->isIdle() ) { offlineImService_->getMessage( pendingOfflineImMessages_.first(), false ); // Processing continues at receivedOfflineIm() } } } // An Offline-IM message was downloaded void MsnNotificationConnection::receivedOfflineIm( const QString &messageId, const QString &from, const QString &to, const QDateTime &date, const QString &body, const QString &runId, int sequenceNum ) { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "Received an offline IM message " << "(from=" << from << " runid=" << runId << " sequenceNum=" << sequenceNum << ")" << endl; #endif #ifdef KMESSTEST KMESS_ASSERT( to == currentAccount_->getHandle() ); #else Q_UNUSED( to ); // Avoid compiler warning #endif // Find the SOAP client to send the next request if( KMESS_NULL(offlineImService_) ) { slotError( i18n( "KMess could not process Offline-IM messages.<br/>Details: %1", i18n("SOAP client is no longer valid.") ), MsnSocketBase::ERROR_INTERNAL ); qDeleteAll( offlineImMessages_ ); offlineImMessages_.clear(); pendingOfflineImMessages_.clear(); return; } // Store message until all messages are received. OfflineImMessage *offlineIm = new OfflineImMessage; offlineIm->messageId = messageId; offlineIm->body = body; offlineIm->date = date; offlineIm->from = from; offlineIm->runId = runId; offlineIm->sequenceNum = sequenceNum; // Order by date uint insertPos = 0; foreach( OfflineImMessage *nextOfflineIm, offlineImMessages_ ) { if( nextOfflineIm->sequenceNum > sequenceNum ) { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "inserted message '" << date << "', #" << sequenceNum << " " << "before '" << nextOfflineIm->date << "', #" << nextOfflineIm->sequenceNum << "." << endl; #endif break; } insertPos++; } offlineImMessages_.insert( insertPos, offlineIm ); // Remove from pending list if( pendingOfflineImMessages_.removeAll( messageId ) == 0 ) { kWarning() << "Could not remove " "message '" << messageId << "' from the list!" << endl; } // If there are more messages, request the next one. if( ! pendingOfflineImMessages_.isEmpty() ) { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "Requesting the next offline-im message."; #endif // Not all messages are received yet, request next offlineImService_->getMessage( pendingOfflineImMessages_.first(), false ); return; } // All messages received, and sorted, // display the messages in the chat windows. #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "All messages received, displaying messages in chat window."; #endif const ContactBase *contact = 0; QString name; QString picture; QStringList messageIds; foreach( OfflineImMessage *offlineIm, offlineImMessages_ ) { messageIds.append( offlineIm->messageId ); // Get contact details // Re-use previous results if message is from the same contact if( contact == 0 || contact->getHandle() != offlineIm->from ) { contact = CurrentAccount::instance()->getContactByHandle( offlineIm->from ); if( contact != 0 ) { // Display the name in a simple way, to make it clearer to // see the CSS style for Offline IM name = contact->getFriendlyName( STRING_CLEANED ); picture = contact->getContactPicturePath(); } else { name = from; picture = QString::null; } } // Send out the chat message emit offlineMessage( ChatMessage( ChatMessage::TYPE_OFFLINE_INCOMING, ChatMessage::CONTENT_MESSAGE, true, offlineIm->body, offlineIm->from, name, picture, QFont(), QString::null, offlineIm->date ) ); } // Delete all local messages qDeleteAll( offlineImMessages_ ); offlineImMessages_.clear(); // Request to remove the Offline-IM message from the online storage. offlineImService_->deleteMessages( messageIds ); // Keep reference to SOAP service so the connection is re-used for other messages. } // Remove a contact from the contact list completely void MsnNotificationConnection::removeContact(QString handle, bool block) { Contact *contact = contactList_->getContactByHandle( handle ); if(KMESS_NULL(contact)) return; QString contactId( contact->getGuid() ); if( contactId.isEmpty() ) { return; } // If the user checks the block option then put the contact in Block Membership list ( if not already present ) if( block && ! contact->isBlocked() ) { // Block the contact blockContact( handle ); } // Remove the contact from AB AddressBookService *addressBook = createAddressBookService();; addressBook->deleteContact( contactId ); } //remove a contact from a single group void MsnNotificationConnection::removeContactFromGroup(QString handle, QString groupId) { AddressBookService *addressBook = createAddressBookService(); Contact *contact = contactList_->getContactByHandle( handle ); if( contact == 0 ) { return; } addressBook->deleteContactFromGroup( contact->getGuid(), groupId ); } // Remove the current media advertising in the personal status. void MsnNotificationConnection::removeCurrentMedia() { lastCurrentMedia_ = QString::null; putUux(); } // Remove a group void MsnNotificationConnection::removeGroup(QString id) { AddressBookService *addressBook = createAddressBookService();; addressBook->deleteGroup( id ); } // Rename a group void MsnNotificationConnection::renameGroup(QString id, QString newName) { AddressBookService *addressBook = createAddressBookService();; addressBook->renameGroup( id, newName ); } // Request a switchboard for a chat with a contact void MsnNotificationConnection::requestSwitchboard( QString handle, ChatInformation::ConnectionType type ) { // Protect again invalid uses. if( handle.isEmpty() ) { kWarning() << "no contact handle set!"; return; } // See if there is a open chat request. foreach( ChatInformation *switchboardInfo, openRequests_ ) { if( switchboardInfo->getContactHandle() == handle ) { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "A pending switchboard for contact " << handle << " is already present." << endl; #endif return; } } // Open directly fake switchboards for offline contacts if( type == ChatInformation::CONNECTION_OFFLINE ) { ChatInformation *offlineChatInfo = new ChatInformation( this, handle, -1, type ); emit startSwitchboard( *offlineChatInfo ); return; } // Send the command int transactionId = sendCommand( "XFR", "SB" ); // Store the pending request information so the return value can be handled. openRequests_.append( new ChatInformation( this, handle, transactionId, type ) ); } // Load the contact list properties void MsnNotificationConnection::readProperties() { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "Loading properties for the notification connection"; #endif // Load the contact list's settings contactList_->readProperties(); } // Save the contact list properties void MsnNotificationConnection::saveProperties() { #ifdef KMESSDEBUG_NOTIFICATION kDebug() << "Saving properties for the notification connection"; #endif // Do not save account properties when it's a temporary account if( currentAccount_->isGuestAccount() ) { kWarning() << "Requested to save properties, but this is a guest account!"; return; } // Save the contact list's settings for this account contactList_->saveProperties(); } /** * @brief The authentication process has timed out * * Called by the login timer, when the login timer has expired */ 02582 void MsnNotificationConnection::slotAuthenticationFailed() { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Connection not successful within time limit."; #endif // Bounce back to the unified error handler to give the user a message slotError( i18n( "Authentication time limit exceeded" ), MsnSocketBase::ERROR_AUTH_TIME_LIMIT ); } // Contact added, notify it to contactlist void MsnNotificationConnection::slotContactAdded( const QString &handle, const QString &contactId, const QStringList &groupsId ) { int lists = Contact::MSN_LIST_ALLOWED | Contact::MSN_LIST_FRIEND; // If the contact already exists, the user requested re-adding to FL list. Contact *contact = contactList_->getContactByHandle( handle ); if( contact != 0 ) { contact->setGuid( contactId ); contact->setFriend( true ); // Ensure to block the contact if "already blocked" if( contact->isBlocked() ) { lists ^= Contact::MSN_LIST_ALLOWED; } } else { contactList_->addContact( handle, handle, lists, QStringList(), contactId ); } // Check if the user has requested that contact is putted into groups if( ! groupsId.isEmpty() ) { foreach( const QString &groupId, groupsId ) { if( ! contactList_->getGroupById( groupId ) ) { continue; } addContactToGroup( handle, groupId ); } } // Ask the server to add the contact to FL and Allow/Block list. putAdl( handle, lists ); } // Contact added to group void MsnNotificationConnection::slotContactAddedToGroup( const QString &contactId, const QString &groupId ) { Contact *contact = contactList_->getContactByGuid( contactId ); if( contact == 0 ) { return; } contact->addGroupId( groupId ); } // Contact blocked void MsnNotificationConnection::slotContactBlocked( const QString &handle ) { Contact *contact = contactList_->getContactByHandle( handle ); if( contact != 0 ) { contact->setBlocked( true ); contact->setAllowed( false ); } } // Contact deleted void MsnNotificationConnection::slotContactDeleted( const QString& contactId ) { Contact *contact = contactList_->getContactByGuid( contactId ); if( contact == 0 ) { return; } QString handle( contact->getHandle() ); contact->setFriend( false ); // Remove it from our FL. putRml( handle, Contact::MSN_LIST_FRIEND ); } // Contact removed from group void MsnNotificationConnection::slotContactDeletedFromGroup( const QString &contactId, const QString &groupId ) { Contact *contact = contactList_->getContactByGuid( contactId ); if( contact == 0 ) { return; } contact->removeGroupId( groupId ); } void MsnNotificationConnection::slotGotAddressBookList( const QList< QHash<QString,QVariant> > &contacts ) { QListIterator<QHash<QString,QVariant> > contact( contacts ); int lists; // Cycle for each contact while( contact.hasNext() ) { // Grep the main informations QHash<QString,QVariant> contactInfo( contact.next() ); const QString& handle( contactInfo.take( "handle" ).toString() ); const QString& friendlyName( contactInfo.take( "friendlyName" ).toString() ); const QString& contactId( contactInfo.take( "contactId" ).toString() ); const QStringList& guidList( contactInfo.take( "guidList" ).toStringList() ); // Add each contact in our AB to contactlist ( FL ) lists = contactsRole_.take( handle ) | Contact::MSN_LIST_FRIEND; Contact *contact = contactList_->addContact( handle, friendlyName, lists , guidList , contactId ); // Set the remaining informations if( contact != 0 ) { contact->setInformations( contactInfo ); } } // If there are contacts not present in AB, but present in Membership List // add them to contactlist if( contactsRole_.count() > 0 ) { QHashIterator< QString, int > i( contactsRole_ ); while( i.hasNext() ) { i.next(); const QString& handle( i.key() ); contactList_->addContact( handle , handle , i.value(), QStringList(), "" ); } contactsRole_.clear(); } putAdl(); } /** * @brief Called when address book services retrieved membership lists. * * In this list there are informations about FL, AL, BL lists. * */ 02749 void MsnNotificationConnection::slotGotMembershipLists( const QString &serviceType, const QHash<QString,int> &contactsRole ) { if( serviceType != "Messenger" ) { kWarning() << "Unable to parse membership list:" << serviceType; return; } contactsRole_ = contactsRole; emit statusMessage( i18n("Waiting for contact list..."), false ); // Allow some time until the next message is received if( loginTimer_.isActive() ) { loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT ); } AddressBookService *addressBook = createAddressBookService();; addressBook->retrieveAddressBook(); } /** * @brief Called when address book services parsed a group. * * @param id the id of the group * @param name the name of the group */ 02779 void MsnNotificationConnection::slotGotGroup( const QString &id, const QString &name ) { contactList_->addGroup( id, name ); } void MsnNotificationConnection::slotContactUnblocked( const QString &handle ) { Contact *contact = contactList_->getContactByHandle( handle ); if( contact != 0 ) { contact->setBlocked( false ); contact->setAllowed( true ); } } // Show warning notifications void MsnNotificationConnection::slotWarning( const QString &warning, bool isImportant ) { if( isImportant ) { KMessageBox::sorry( 0, i18nc( "Connection warning: dialog box with message", "<p><i>Warning</i>: %1</p>", warning ), i18nc( "Error dialog box title", "MSN Warning" ) ); } else { // Save the error details in the event settings NotificationManager::EventSettings settings; settings.sender = this; settings.contact = 0; settings.widget = 0; settings.buttons = NotificationManager::BUTTON_HIDE; // Notify the user about the network error NotificationManager::instance()->notify( "status", warning, settings ); } } // Unblock the given contact void MsnNotificationConnection::unblockContact( QString handle ) { putRml( handle, Contact::MSN_LIST_BLOCKED ); putAdl( handle, Contact::MSN_LIST_ALLOWED ); AddressBookService *addressBook = createAddressBookService(); addressBook->unblockContact( handle ); } /** * @brief Called when the connection has been established. * * This method calls putVer() to start the version exchange. */ 02841 void MsnNotificationConnection::slotConnected() { #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Connected to server, sending VER."; #endif // Start the login timer, allowing the defined maximum time between each // received command loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT ); putVer(); } /** * @brief Shows error dialog boxes * * Closes the connection, and displays * a suitable type of message box. * * @param error The error reason or explanation * @param type The type of error */ 02866 void MsnNotificationConnection::slotError( QString error, MsnSocketBase::ErrorType type ) { // Always report errors to console kWarning() << "KMess Error ( type" << type << "):" << error << "!"; QString message; QString additionalInfo; QString detailedMessage; bool tryReconnecting = true; bool useNotification = true; #ifdef KMESSDEBUG_NOTIFICATION_GENERAL // Add more details about the error in developer builds. additionalInfo = i18n( "<br />Internal error reason: %1", error ); #endif #if KMESS_ENABLE_NULL_SOCKET == 0 // If the server can't be contacted, try again with HTTP before giving up if( ( isInitiatingConnection_ && type == MsnSocketBase::ERROR_DROP ) || type == MsnSocketBase::ERROR_CONNECTING || type == MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT ) { if( switchToHttpSocket() ) { // Note how the first error is silently discarded. If it's something important, like // the "server is too busy" error, it'll be received when connecting via HTTP anyways. return; } } #endif // Clean up the mess left by the SOAP classes if( qobject_cast<OfflineImService*>( sender() ) != 0 ) { // No need to request more, it just failed. qDeleteAll( offlineImMessages_ ); offlineImMessages_.clear(); } // First disconnect closeConnection(); // Then decide what kind of message to show switch( type ) { // Notify that connecting has failed due to a wrong handle or password case MsnSocketBase::ERROR_AUTH_LOGIN: // This is an immediate error, a dialog box is needed KMessageBox::error( 0, i18nc( "Connection error: dialog box", "<p>Authentication has failed, please verify " "your account email and password.</p>" ), i18nc( "Error dialog box title", "MSN Error" ) ); // Do not attempt to reconnect tryReconnecting = false; // Don't send a passive notification useNotification = false; break; // The user has connected this account from another location case MsnSocketBase::ERROR_CONNECTION_OTHER: message = i18nc( "Connection error: passive notification message", "<p>The account <i>%1</i> has been connected from another location.</p>", currentAccount_->getHandle() ); detailedMessage = i18nc( "Connection error: detailed message", "<p>You have been disconnected: you have connected " "with the account <i>%1</i> from another Messenger client, " "or from another location.</p>", currentAccount_->getHandle() ); // Do not attempt to reconnect tryReconnecting = false; break; // The HTTP connection failed: there's a redirection page case MsnSocketBase::ERROR_CONNECTING_GATEWAY: message = i18nc( "Connection error: passive notification message", "<p>Unable to connect to the Live Messenger service.<br/>" "Maybe you need to authenticate before you can access the network?</p>" ); detailedMessage = i18nc( "Connection error: detailed message", "<p>KMess could not connect to the Live Messenger servers.<br />" "There may be a problem with your Internet connection, " "or the Live Messenger servers may be temporarily unavailable.<br/>" "It is also possible that an authentication to a web page or proxy " "may be required to access the network.</p>" "<p><a href='%1'>Click here</a> to visit the Messenger service " "status page.</p>", "http://status.messenger.msn.com/Status.aspx" ); // Do not attempt to reconnect tryReconnecting = false; break; // Continue as for authentication errors if the switch to HTTP had failed case MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT: case MsnSocketBase::ERROR_CONNECTING: case MsnSocketBase::ERROR_AUTH_TIME_LIMIT: case MsnSocketBase::ERROR_SOAP_TIME_LIMIT: message = i18nc( "Connection error: passive notification message", "<p>Unable to connect to the Live Messenger service.</p>" ); detailedMessage = i18nc( "Connection error: detailed message", "<p>KMess could not connect to the Live Messenger servers.<br />" "There may be a problem with your Internet connection, " "or the Live Messenger servers may be temporarily unavailable.</p>" "<p><a href='%1'>Click here</a> to visit the Messenger service " "status page.</p>", "http://status.messenger.msn.com/Status.aspx" ); break; // Report errors caused by the user case MsnSocketBase::ERROR_USER: message = i18nc( "Connection error (Server-reported user error): passive notification message", "<p><i>Error</i>: %1</p>", error ); detailedMessage = i18nc( "Connection error (Server-reported user error): detailed message", "<p>The Live Messenger server has reported an error:</p>" "<p>%1</p>", error ); break; // Report internal server errors case MsnSocketBase::ERROR_SERVER: case MsnSocketBase::ERROR_SOAP_TOOMANYREDIRECTS: case MsnSocketBase::ERROR_SOAP_RESPONSE: case MsnSocketBase::ERROR_SOAP_AUTHENTICATION: message = i18nc( "Connection error (Server-reported server error): passive notification message", "<p><i>Messenger Service Error</i>: %1</p>", error ); detailedMessage = i18nc( "Connection error (Server-reported server error): detailed message", "<p>The Live Messenger server has reported an error:</p>" "<p>%1</p>", error ); break; // Report internal KMess errors, usually after receiving an error by the MSN server case MsnSocketBase::ERROR_INTERNAL: message = i18nc( "Connection error (Server-reported client error): passive notification message", "<p><i>KMess Error</i>: %1</p>", error ); detailedMessage = i18nc( "Connection error (Server-reported client error): detailed message", "<p>KMess has encountered an internal error:</p>" "<p>%1</p>", error ); break; case MsnSocketBase::ERROR_DROP: case MsnSocketBase::ERROR_DATA: case MsnSocketBase::ERROR_UNKNOWN: case MsnSocketBase::ERROR_SOAP_UNKNOWN: default: message = i18nc( "Connection error: passive notification message", "<p>Network connection lost.</p>" ); detailedMessage = i18nc( "Connection error: detailed message", "<p>Connection to the Live Messenger server has been lost.</p>" ); break; } if( useNotification ) { // Save the error details in the event settings QMap<QString,QVariant> details; details[ "errorType" ] = (int) type; details[ "errorString" ] = error; details[ "detailedMessage" ] = detailedMessage; NotificationManager::EventSettings settings; settings.sender = this; settings.contact = 0; settings.widget = 0; settings.buttons = NotificationManager::BUTTON_HIDE | NotificationManager::BUTTON_MORE_DETAILS; settings.data = details; // Notify the user about the network error NotificationManager::instance()->notify( "status", message, settings ); } // Try connecting again if( tryReconnecting ) { emit reconnect( currentAccount_->getHandle() ); } } // Displays additional info about network errors void MsnNotificationConnection::slotErrorEventActivated( NotificationManager::EventSettings settings, NotificationManager::Buttons button ) { // The signal wasn't meant for us if( settings.sender != this ) { return; } KDialog *dialog; QString developerInfo; const QMap<QString,QVariant> &info = settings.data.toMap(); switch( button ) { case NotificationManager::BUTTON_MORE_DETAILS: #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Showing error details"; #endif #ifdef KMESSDEBUG_NOTIFICATION_GENERAL // Add more details about the error in developer builds. developerInfo = i18nc( "Developer details placed on the network error dialog box", "<p><b>Developer info:</b><br/>" "<i>Error number:</i> %1<br/>" "<i>Error string:</i> %2" "</p>", info["errorType" ].toString(), info["errorString"].toString() ); #endif // We need to create a non-modal informative message box: a modal one would block // execution here, and if the user dismisses the box after the notification has // already been deleted by KDE, kmess would crash. dialog = new KDialog( 0 ); dialog->setAttribute( Qt::WA_DeleteOnClose ); dialog->setButtons( KDialog::Ok ); KMessageBox::createKMessageBox( dialog, QMessageBox::Information, info["detailedMessage"].toString() + developerInfo, QStringList(), QString(), 0, KMessageBox::Notify | KMessageBox::AllowLink | KMessageBox::NoExec, QString() ); dialog->show(); break; case NotificationManager::BUTTON_HIDE: #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Hiding status notification"; #endif // Do nothing break; default: #ifdef KMESSDEBUG_NOTIFICATION_GENERAL kDebug() << "Invalid button" << button; #endif return; } } /** * @brief Unlock notifications after the first seconds of connections * * The first moments KMess is connected notifications are blocked, to avoid receiving popups before * the user could even look at the contact list, possibly distracting him/her or covering screen space. */ 03121 void MsnNotificationConnection::unlockNotifications() { enableNotifications_ = true; } #include "msnnotificationconnection.moc"