Main Page | Namespace List | Class Hierarchy | Class List | Directories | File List | Namespace Members | Class Members | File Members

Handshake.cpp

Go to the documentation of this file.
00001 //
00002 // Handshake.cpp
00003 //
00004 // Copyright (c) Shareaza Development Team, 2002-2005.
00005 // This file is part of SHAREAZA (www.shareaza.com)
00006 //
00007 // Shareaza is free software; you can redistribute it
00008 // and/or modify it under the terms of the GNU General Public License
00009 // as published by the Free Software Foundation; either version 2 of
00010 // the License, or (at your option) any later version.
00011 //
00012 // Shareaza is distributed in the hope that it will be useful,
00013 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 // GNU General Public License for more details.
00016 //
00017 // You should have received a copy of the GNU General Public License
00018 // along with Shareaza; if not, write to the Free Software
00019 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00020 //
00021 
00022 // CHandshake figures out what the remote computer wants from the first 7 bytes it sends us
00023 // http://wiki.shareaza.com/static/Developers.Code.CHandshake
00024 
00025 // Copy in the contents of these files here before compiling
00026 #include "StdAfx.h"
00027 #include "Shareaza.h"
00028 #include "Settings.h"
00029 #include "Handshakes.h"
00030 #include "Handshake.h"
00031 #include "Neighbours.h"
00032 #include "Downloads.h"
00033 #include "Uploads.h"
00034 #include "UploadTransfer.h"
00035 #include "ChatCore.h"
00036 #include "Network.h"
00037 #include "Buffer.h"
00038 #include "GProfile.h"
00039 #include "BTClients.h"
00040 #include "EDClients.h"
00041 #include "EDPacket.h"
00042 #include "WndMain.h"
00043 #include "WndChat.h"
00044 
00045 // If we are compiling in debug mode, replace the text "THIS_FILE" in the code with the name of this file
00046 #ifdef _DEBUG
00047 #undef THIS_FILE
00048 static char THIS_FILE[]=__FILE__;
00049 #define new DEBUG_NEW
00050 #endif
00051 
00053 // CHandshake construction
00054 
00055 // Make a new CHandshake object
00056 // Initializes the member variables with default values
00057 CHandshake::CHandshake()
00058 {
00059         // We did not connect to the remote computer as part of a push
00060         m_bPushing = FALSE;
00061 
00062         // Set pointers so the input and output bandwidth limits are read from the DWORD in settings
00063         m_mInput.pLimit = m_mOutput.pLimit = &Settings.Bandwidth.Request;
00064 }
00065 
00066 // Called when a remote computer wants to connect to us
00067 // Make a new CHanshake object given a socket, and a MFC SOCKADDR_IN structure which contains an IP address and port number
00068 // Uses AcceptFrom to make a new object with this socket and ip address
00069 CHandshake::CHandshake(SOCKET hSocket, SOCKADDR_IN* pHost)
00070 {
00071         // We did not connect to the remote computer as part of a push
00072         m_bPushing = FALSE;
00073 
00074         // Call CConnection::AcceptFrom to setup this object with the socket and
00075         AcceptFrom( hSocket, pHost );
00076 
00077         // Set pointers so the input and output bandwidth limits are read from the DWORD in settings
00078         m_mInput.pLimit = m_mOutput.pLimit = &Settings.Bandwidth.Request;
00079 
00080         // Record that the program accepted this connection
00081         theApp.Message( MSG_DEFAULT, IDS_CONNECTION_ACCEPTED, (LPCTSTR)m_sAddress, htons( m_pHost.sin_port ) );
00082 }
00083 
00084 // Copy a CHandshake object
00085 // Takes pCopy, a pointer to the CHandshake object to make this new one a copy of
00086 CHandshake::CHandshake(CHandshake* pCopy)
00087 {
00088         // Call CConnection::AttachTo to copy the CConnection core of pCopy over to this new CHandshake object
00089         AttachTo( pCopy );
00090 
00091         // Then, copy across the CHandshake member variables, since AttachTo just does the CConnection ones
00092         m_bPushing              = pCopy->m_bPushing;    // Copy in whether or not we connected to the remote computer as part of a push
00093         m_nIndex                = pCopy->m_nIndex;              // Copy across the handshake index (do)
00094 
00095         // Set pointers so the input and output bandwidth limits are read from the DWORD in settings
00096         m_mInput.pLimit = m_mOutput.pLimit = &Settings.Bandwidth.Request;
00097 }
00098 
00099 // Delete this CHandshake object
00100 CHandshake::~CHandshake()
00101 {
00102         // There is nothing the CConnection destructor won't take care of
00103 }
00104 
00106 // CHandshake push
00107 
00108 // Open a connection to a new remote computer
00109 // Takes the IP address and port number of the remote computer, as well as the index (do)
00110 // Connects this socket to the new computer, but does not make any communications yet
00111 BOOL CHandshake::Push(IN_ADDR* pAddress, WORD nPort, DWORD nIndex)
00112 {
00113         // Report we are about to push open a connection to the given IP address and port number now
00114         theApp.Message( MSG_DEFAULT, IDS_UPLOAD_CONNECT, (LPCTSTR)CString( inet_ntoa( *pAddress ) ), _T("") );
00115 
00116         // Connect the socket in this CHandshake object to the IP address and port number the method got passed
00117         if ( ! ConnectTo( pAddress, nPort ) ) return FALSE; // If the connection was not made, leave now
00118 
00119         // Tell Windows to give us a m_pWakeup event if this socket connects, gets data, is ready for writing, or closes
00120         WSAEventSelect(                         // Specify our event object Handshakes.m_pWakeup to the FD_CONNECT FD_READ FD_WRITE and FD_CLOSE events
00121                 m_hSocket,                              // The socket in this CHandshake object
00122                 Handshakes.m_pWakeup,   // The MFC CEvent object we've made for the wakeup event
00123                 FD_CONNECT      |                       // The connection has been made
00124                 FD_READ         |                       // There is data from the remote computer on our end of the socket waiting for us to read it
00125                 FD_WRITE        |                       // The remote computer is ready for some data from us (do)
00126                 FD_CLOSE );                             // The socket has been closed
00127 
00128         // Record the push in the member variables
00129         m_bPushing              = TRUE;                         // We connected to this computer as part of a push
00130         m_tConnected    = GetTickCount();       // Record that we connected right now
00131         m_nIndex                = nIndex;                       // Copy the given index number into this object (do)
00132 
00133         // Report success
00134         return TRUE;
00135 }
00136 
00138 // CHandshake run event
00139 
00140 // Determine the handshake has been going on for too long
00141 // Returns true or false
00142 BOOL CHandshake::OnRun()
00143 {
00144         // If we've been connected for longer than the handshake timeout from settings
00145         if ( GetTickCount() - m_tConnected > Settings.Connection.TimeoutHandshake )
00146         {
00147                 // Report this, and return false
00148                 theApp.Message( MSG_ERROR, IDS_HANDSHAKE_TIMEOUT, (LPCTSTR)m_sAddress );
00149                 return FALSE;
00150         }
00151 
00152         // We still have time for the handshake
00153         return TRUE;
00154 }
00155 
00157 // CHandshake connection events
00158 
00159 // Send the remote computer our client index and guid in a header like "GIV index:guid/"
00160 BOOL CHandshake::OnConnected()
00161 {
00162         // Call CConnection's OnConnected method first, even though it does nothing (do)
00163         CConnection::OnConnected();
00164 
00165         // copy Profile's GUID
00166         GGUID oID( MyProfile.GUID );
00167 
00168         // Compose the GIV string, which is like "GIV index:guid/" with two newlines at the end (do)
00169         CString strGIV;
00170         strGIV.Format( // MFC's CString::Format is like sprintf, "%.2X" formats a byte into 2 hexidecimal characters like "ff"
00171                 _T("GIV %u:%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X/\n\n"),
00172                 m_nIndex,                                                                                       // Our index on the Gnutella network (do)
00173                 int( oID.n[0] ),  int( oID.n[1] ),  int( oID.n[2] ),  int( oID.n[3] ),          // Our GUID
00174                 int( oID.n[4] ),  int( oID.n[5] ),  int( oID.n[6] ),  int( oID.n[7] ),
00175                 int( oID.n[8] ),  int( oID.n[9] ),  int( oID.n[10] ), int( oID.n[11] ),
00176                 int( oID.n[12] ), int( oID.n[13] ), int( oID.n[14] ), int( oID.n[15] ) );
00177 
00178         // Print the string into the output buffer, and write the output buffer to the remote computer
00179         m_pOutput->Print( strGIV );
00180         OnWrite();
00181 
00182         // Record that we uploaded the giv, and report success
00183         theApp.Message( MSG_DEFAULT, IDS_UPLOAD_GIV, (LPCTSTR)m_sAddress );
00184         return TRUE;
00185 }
00186 
00187 // If we connected to the remote computer as part of a push, record that we couldn't connect to do the upload
00188 void CHandshake::OnDropped(BOOL bError)
00189 {
00190         // If we connected to the remote computer as part of a push
00191         if ( m_bPushing )
00192         {
00193                 // Record a upload connect error
00194                 theApp.Message( MSG_ERROR, IDS_UPLOAD_CONNECT_ERROR, (LPCTSTR)m_sAddress );
00195         }
00196 }
00197 
00199 // CHandshake read event
00200 
00201 // Reads the first few bytes from the other computer to figure out what it wants
00202 // Returns true if it needs more information, false if it's done
00203 BOOL CHandshake::OnRead()
00204 {
00205         // Read data waiting in the socket into the input buffer
00206         CConnection::OnRead();
00207 
00208         // We need at least 7 bytes of headers from the remote compuer to figure out what network its talking about
00209         if ( m_pInput->m_nLength < 7 ) return TRUE; // Not enough information yet, leave now returning true
00210 
00211         // Determine if the remote computer has sent an eDonkey2000 hello packet
00212         if ( m_pInput->m_nLength >= 7                                                   && // 7 or more bytes have arrived
00213                  m_pInput->m_pBuffer[0] == ED2K_PROTOCOL_EDONKEY        && // The first byte is "e3", indicating eDonkey2000
00214                  m_pInput->m_pBuffer[5] == ED2K_C2C_HELLO                       && // 5 bytes in is "01", a hello for that network
00215                  m_pInput->m_pBuffer[6] == 0x10 )                                          // And after that is "10"
00216         {
00217                 // Have the EDClients object accept this CHandshake as a new eDonkey2000 computer
00218                 EDClients.OnAccept( this );
00219                 return FALSE; // Return false to indicate that we are done sorting the handshake
00220         }
00221 
00222         // See if the remote computer is speaking BitTorrent
00223         if ( m_pInput->m_nLength >= BT_PROTOCOL_HEADER_LEN && // We have at least 20 bytes
00224                  memcmp( m_pInput->m_pBuffer, BT_PROTOCOL_HEADER, BT_PROTOCOL_HEADER_LEN ) == 0 ) // They are "\023BitTorrent protocol"
00225         {
00226                 // Have the BTClients object accept this CHandshake as a new BitTorrent computer
00227                 BTClients.OnAccept( this );
00228                 return FALSE; // Done sorting the handshake
00229         }
00230 
00231         // With eDonkey2000 and BitTorrent out of the way, now we can look for text-based handshakes
00232 
00233         // Read the first header line
00234         CString strLine;
00235         if ( ! m_pInput->ReadLine( strLine, TRUE ) ) // Read characters until \n, returning false if there is no \n
00236         {
00237                 // The remote computer hasn't sent a \n yet, if there are more than 2048 bytes in the first line, abort
00238                 return ( m_pInput->m_nLength < 2048 ) ? TRUE : FALSE; // Return false to signal we are done sorting the handshake
00239         }
00240 
00241         // The first line the remote computer sent was blank
00242         if ( strLine.IsEmpty() )
00243         {
00244                 // Eat blank lines, just read the next line into strLine and return true
00245                 m_pInput->ReadLine( strLine ); // But strLine is never used? (do)
00246                 return TRUE; // Keep trying to sort the handshake
00247         }
00248 
00249         // Record this handshake line as an application message
00250         theApp.Message( MSG_DEBUG, _T("%s: HANDSHAKE: %s"), (LPCTSTR)m_sAddress, (LPCTSTR)strLine );
00251 
00252         // The first header starts "GET" or "HEAD"
00253         if ( _tcsncmp( strLine, _T("GET"), 3 ) == 0 || _tcsncmp( strLine, _T("HEAD"), 4 ) == 0 )
00254         {
00255                 // The remote computer wants a file from us, accept the connection as an upload
00256                 Uploads.OnAccept( this, strLine );
00257         }
00258         else if ( _tcsnicmp( strLine, _T("GNUTELLA"), 8 ) == 0 ) { Neighbours.OnAccept( this ); }       // Gnutella handshake
00259         else if ( _tcsnicmp( strLine, _T("PUSH "),    5 ) == 0 ) { OnAcceptPush(); }                            // Gnutella2-style push
00260         else if ( _tcsnicmp( strLine, _T("GIV "),     4 ) == 0 ) { OnAcceptGive(); }                            // Gnutella giv
00261         else if ( _tcsnicmp( strLine, _T("CHAT"),     4 ) == 0 )                                                                        // Chat
00262         {
00263                 // If the user has setup a valid profile and enabeled chat in the program settings
00264                 if ( MyProfile.IsValid() && Settings.Community.ChatEnable )
00265                 {
00266                         // Have the chat system accept this connection
00267                         ChatCore.OnAccept( this );
00268                 }
00269                 else
00270                 {
00271                         // Otherwise, tell the other computer we can't chat
00272                         m_pOutput->Print( "CHAT/0.2 404 Unavailable\r\n\r\n" );
00273                         OnWrite();
00274                 }
00275         }
00276         else
00277         {
00278                 // The first header starts with something else, report that we couldn't figure out the handshake
00279                 theApp.Message( MSG_ERROR, IDS_HANDSHAKE_FAIL, (LPCTSTR)m_sAddress );
00280         }
00281 
00282         // Return false to indicate that we are done sorting the handshake
00283         return FALSE;
00284 }
00285 
00287 // CHandshake accept Gnutella2-style PUSH
00288 
00289 // When the first thing a remote computer says is a Gnutella2 "PUSH", this method gets called
00290 // Checks if a child window recognizes the guid
00291 // Returns true or false
00292 BOOL CHandshake::OnAcceptPush()
00293 {
00294         // Make a string for the header line, and variables to hold the GUID in string and binary forms
00295         CString strLine, strGUID;
00296         GGUID pGUID;
00297 
00298         // Read the first line from the input buffer, this doesn't remove it so we can call it over and over again
00299         if ( ! m_pInput->ReadLine( strLine ) ) return FALSE;
00300 
00301         // Make sure the line has the format "PUSH guid:GUIDinHEXguidINhexGUIDinHEXguidI", which has 10 characters before the 32 for the guid
00302         if ( strLine.GetLength() != 10 + 32 )
00303         {
00304                 // Report the bad push and return reporting error
00305                 theApp.Message( MSG_ERROR, IDS_DOWNLOAD_BAD_PUSH, (LPCTSTR)CString( inet_ntoa( m_pHost.sin_addr ) ) );
00306                 return FALSE;
00307         }
00308 
00309         // Read the 16 hexidecimal digits of the GUID, copying it into pGUID
00310         for ( int nByte = 0 ; nByte < 16 ; nByte++ )
00311         {
00312                 int nValue;
00313                 _stscanf( strLine.Mid( 10 + nByte * 2, 2 ), _T("%X"), &nValue );
00314                 pGUID.n[ nByte ] = (BYTE)nValue;
00315         }
00316 
00317         // If a child window recongizes the GUID, accept the push
00318         if ( OnPush( &pGUID ) ) return TRUE;
00319 
00320         // Record the fact that we got a push we knew nothing about, and return false to not accept it
00321         theApp.Message( MSG_ERROR, IDS_DOWNLOAD_UNKNOWN_PUSH, (LPCTSTR)CString( inet_ntoa( m_pHost.sin_addr ) ), _T("Gnutella2") );
00322         return FALSE;
00323 }
00324 
00326 // CHandshake accept Gnutella1-style GIV
00327 
00328 // If the first thing the remote computer says to us is a Gnutella "GIV ", this method gets called
00329 // Checks if a child window recognizes the guid
00330 // Returns true or false
00331 BOOL CHandshake::OnAcceptGive()
00332 {
00333         // Local variables for the searching and parsing
00334         CString strLine, strClient, strFile;    // Strings for the whole line, the client guid hexidecimal characters, and the file name within it
00335         DWORD nFileIndex = 0xFFFFFFFF;                  // Start out the file index as -1 to detect if we were able to read it
00336         GGUID pClientID;                                                // We will translate the GUID into binary here
00337         int nPos;                                                               // The distance from the start of the string to a colon or slash we will look for
00338 
00339         // The first line should be like "GIV 124:d51dff817f895598ff0065537c09d503/my%20song.mp3"
00340         if ( ! m_pInput->ReadLine( strLine ) ) return FALSE; // If the line isn't all there yet, return false to try again later
00341 
00342         // If there is a slash in the line
00343         if ( ( nPos = strLine.Find( '/' ) ) > 0 ) // Find returns the 0-based index in the string, -1 if not found
00344         {
00345                 // Clip out the part of the line after the slash, URL decode it to turn %20 into spaces, and save that in strFile
00346                 strFile = URLDecode( strLine.Mid( nPos + 1 ) ); // Mid takes part after the slash
00347                 strLine = strLine.Left( nPos );                                 // Left removes that part and the slash from strLine
00348         }
00349 
00350         // If there is a colon in the line more than 4 character widths in, like "GIV 123:client32characterslong----------"
00351         if ( ( nPos = strLine.Find( ':' ) ) > 4 )
00352         {
00353                 // Read the number before and text after the colon
00354                 strClient       = strLine.Mid( nPos + 1 );                      // Clip out just the "client" part of the example shown above
00355                 strLine         = strLine.Mid( 4, nPos - 4 );           // Starting at 4 to get beyond the "GIV ", clip out "123", any text before the colon at nPos
00356                 _stscanf( strLine, _T("%lu"), &nFileIndex );    // Read "123" as a number, this is the file index
00357         }
00358 
00359         // If there was no colon and the file index was not read, or the client text isn't exactly 32 characters long
00360         if ( nFileIndex == 0xFFFFFFFF || strClient.GetLength() != 32 )
00361         {
00362                 // Make a record of this bad push and leave now
00363                 theApp.Message( MSG_ERROR, IDS_DOWNLOAD_BAD_PUSH, (LPCTSTR)CString( inet_ntoa( m_pHost.sin_addr ) ) );
00364                 return FALSE;
00365         }
00366 
00367         // The client id text is a 16-byte guid written in 32 characters with text like "00" through "ff" for each byte, read it into pClientID
00368         for ( int nByte = 0 ; nByte < 16 ; nByte++ )
00369         {
00370                 // Convert one set of characters like "00" or "ff" into that byte in pClientID
00371                 _stscanf( strClient.Mid( nByte * 2, 2 ), _T("%X"), &nPos );
00372                 pClientID.n[ nByte ] = (BYTE)nPos;
00373         }
00374 
00375         // If a child window recognizes this guid, return true
00376         if ( OnPush( &pClientID ) ) return TRUE;
00377 
00378         // If the file name is longer than 256 characters, change it to the text "Invalid Filename"
00379         if ( strFile.GetLength() > 256 ) strFile = _T("Invalid Filename");
00380 
00381         // Log this unexpected push, and return false
00382         theApp.Message( MSG_ERROR, IDS_DOWNLOAD_UNKNOWN_PUSH, (LPCTSTR)CString( inet_ntoa( m_pHost.sin_addr ) ), (LPCTSTR)strFile );
00383         return FALSE;
00384 }
00385 
00387 // CHandshake accept a push from a GUID
00388 
00389 // Takes the GUID of a remote computer which has sent us a Gnutella2-style PUSH handshake
00390 // Sees if any child windows recognize the GUID
00391 // Returns true or false
00392 BOOL CHandshake::OnPush(GGUID* pGUID)
00393 {
00394         // Make sure the socket is valid
00395         if ( m_hSocket == INVALID_SOCKET ) return FALSE;
00396 
00397         // Look for the remote computer's GUID in our list of downloads and the chat interface
00398         if ( Downloads.OnPush( pGUID, this ) ) return TRUE; // Return true if it's found
00399         if ( ChatCore.OnPush( pGUID, this ) ) return TRUE;
00400 
00401         // Make sure this is the only thread doing this right now
00402         CSingleLock pWindowLock( &theApp.m_pSection );
00403         if ( pWindowLock.Lock( 250 ) ) // Don't wait here for more than a quarter second, Lock will return false and so will OnPush
00404         {
00405                 // Access granted, get a pointer to the windowing system
00406                 if ( CMainWnd* pMainWnd = theApp.SafeMainWnd() )
00407                 {
00408                         // Get a pointer to the main Shareaza window
00409                         CWindowManager* pWindows        = &pMainWnd->m_pWindows;
00410                         CChildWnd* pChildWnd            = NULL;
00411 
00412                         // Loop through all of Shareaza's child windows
00413                         while ( pChildWnd = pWindows->Find( NULL, pChildWnd ) )
00414                         {
00415                                 // If a child window recognizes this push request, return true
00416                                 if ( pChildWnd->OnPush( pGUID, this ) ) return TRUE;
00417                         }
00418                 }
00419 
00420                 // Let other threads use theApp.m_pSection
00421                 pWindowLock.Unlock();
00422         }
00423 
00424         // No child window recognized a push request, or we waited more than a quarter second and gave up
00425         return FALSE;
00426 }

Generated on Thu Dec 15 10:39:43 2005 for Shareaza 2.2.1.0 by  doxygen 1.4.2