/*++ Copyright (c) 2000 Microsoft Corporation. All rights reserved. --*/ // SocksConnection.cpp : Implementation of CSocksConnection #include "stdafx.h" #include "Socksfltr.h" #include "SocksConnection.h" #include "Socksfilter.h" #include "outputdebugstringf.h" #include "FilterDataStructures.h" #include ///////////////////////////////////////////////////////////////////////////// // CSocksConnection // The structure of socks packets. struct SOCKS5First { BYTE VER; // version BYTE ANS; // Answer }; struct SOCKS5Request { BYTE VER; // version BYTE CMD; // Command Code BYTE RES; // Reserved BYTE ATYP; // Address type union ADDRESS { BYTE LEN; // len of the domain name struct IPORT { DWORD ip; USHORT port; } IpAndPort; } Addr; }; #pragma pack(1) struct SOCKS5Answer { BYTE VER; // version BYTE REP; // Command Code BYTE RES; // Reserved BYTE ATYP; // Address type DWORD ip; // IP USHORT port; // PORT }; #pragma pack() // Outside Server made a connection to the proxy. HRESULT STDMETHODCALLTYPE CSocksConnection::CompleteAsyncAccept( /* [in] */ BOOL fSuccess, /* [in] */ DWORD Win32ErrorCode, /* [in] */ IFWXNetworkSocket __RPC_FAR *pListeningSocket, /* [in] */ IFWXNetworkSocket __RPC_FAR *pAcceptSocket, /* [in] */ LPSOCKADDR RemoteAddress, /* [in] */ DWORD RemoteAddressLength, /* [in] */ LPSOCKADDR LocalAddress, /* [in] */ DWORD LocalAddressLength, /* [in] */ UserContextType UserData) { UNREFERENCED_PARAMETER(UserData); UNREFERENCED_PARAMETER(RemoteAddressLength); UNREFERENCED_PARAMETER(LocalAddressLength); UNREFERENCED_PARAMETER(LocalAddress); UNREFERENCED_PARAMETER(RemoteAddress); UNREFERENCED_PARAMETER(pListeningSocket); UNREFERENCED_PARAMETER(Win32ErrorCode); UNREFERENCED_PARAMETER(fSuccess); SOCKADDR ServAddr = { 0 }; int len; len = sizeof(ServAddr); // // we must make sure the external socket is not closed already (due to client // aborting session) before we replace the listening socket with accepted socket // Lock(); // add-ref listening socket, so we can close it outside the lock CComPtr spListenSocket = m_spExternalSocket; if (m_spExternalSocket != NULL) { m_spExternalSocket = pAcceptSocket; m_spExternalSocket->GetPeerName(&ServAddr,&len); } Unlock(); if (spListenSocket != NULL) { spListenSocket->Close(TRUE); // Send a successfull second Reply SendReplyB(CONN_SUCC, ServAddr, FALSE); } return S_OK; } /* */ HRESULT STDMETHODCALLTYPE CSocksConnection::CompleteAsyncIO( /* [in] */ BOOL fSuccess, /* [in] */ DWORD Win32ErrorCode, /* [in] */ IFWXIOBuffer __RPC_FAR *pIOBuffer, /* [in] */ UserContextType UserData, /* [in] */ LPSOCKADDR ExternalAddress, /* [in] */ INT ExternalAddressLength) { UNREFERENCED_PARAMETER(ExternalAddressLength); UNREFERENCED_PARAMETER(ExternalAddress); UNREFERENCED_PARAMETER(Win32ErrorCode); HRESULT hr; BYTE *pBuffer; DWORD dwBufferSize; // Abort the connection if anything fails. if (!fSuccess) { Abort(); return S_OK; } // Get the pointer and size; hr = pIOBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr) || dwBufferSize == 0) { CloseSockets(FALSE); return S_OK; } Lock(); CComPtr spClientSocket = m_spClientSocket; CComPtr spExternalSocket = m_spExternalSocket; Unlock(); if (spClientSocket == NULL) { // sockets already closed return S_OK; } // Select the next action switch (UserData) { // ready to read the command case eSendAnsToNegotiation : m_spCmdBuffer.Release(); hr = m_spWspProxy->CreateBuffer(MAX_CMD_LENGTH, &m_spCmdBuffer); if (FAILED(hr)) { Abort(); } hr = spClientSocket->Recv(m_spCmdBuffer, this, eGetCmd); if (FAILED(hr)) { Abort(); } break; // got and replayed first packet case eSendAnsToProposal : m_spCmdBuffer.Release(); hr = m_spWspProxy->CreateBuffer(MAX_CMD_LENGTH, &m_spCmdBuffer); if (FAILED(hr)) { Abort(); } // Get the user and pwd data hr = spClientSocket->Recv(m_spCmdBuffer, this, eGetNegotiation); if (FAILED(hr)) { Abort(); } break; // check the user and password got from client case eGetNegotiation: hr = HandleNegotiation(); if (FAILED(hr)) { Abort(); } break; // check the first packet with the proposal for authentication case eGetProposal: hr = HandleProposal(); if (FAILED(hr)) { Abort(); } break; case eGetCmd: hr = CommandHandler(); if (FAILED(hr)) { Abort(); } break; case eSendError: CloseSockets(FALSE); break; case eFirstSuccess: // Begin data pump hr = spClientSocket->Recv(NULL, this, eReadFromClient); if (FAILED(hr)) { Abort(); } break; case eSendSuccess: // Begin data pump hr = spClientSocket->Recv(NULL, this, eReadFromClient); if (FAILED(hr)) { Abort(); } hr = spExternalSocket->Recv(NULL, this, eReadFromInet); if (FAILED(hr)) { Abort(); } break; // // Data pump // case eReadFromInet: hr = spClientSocket->Send(pIOBuffer, this, eWriteToClient); if (FAILED(hr)) { Abort(); } break; case eReadFromClient: hr = spExternalSocket->Send(pIOBuffer, this, eWriteToInet); if (FAILED(hr)) { Abort(); } break; case eWriteToInet: hr = spClientSocket->Recv(NULL, this, eReadFromClient); if (FAILED(hr)) { Abort(); } break; case eWriteToClient: hr = spExternalSocket->Recv(NULL, this, eReadFromInet); if (FAILED(hr)) { Abort(); } break; default: Abort(); } return S_OK; } HRESULT CSocksConnection::Initialize( Csocksfilter *socksfilter, LPSOCKADDR ClientAddress, IFWXNetworkSocket *pSocket, IFWXFirewall *WspProxy) { HRESULT hr; BOOL fBool=TRUE; // Allocate a buffer hr = WspProxy->CreateBuffer(MAX_CMD_LENGTH, &m_spCmdBuffer); if (FAILED(hr)) { return hr; } m_token = NULL; // Keep a reference to the socket and the proxy. m_spWspProxy = WspProxy; m_spClientSocket = pSocket; m_ClientAddress = *ClientAddress; socksfilter->AddRef(); m_pSocksfilter = socksfilter; hr = m_spClientSocket->SetSockOpt(IPPROTO_TCP,TCP_NODELAY,(char*)&fBool,sizeof(BOOL)); // Issue first receive hr = pSocket->Recv(m_spCmdBuffer, this, eGetProposal); if (FAILED(hr)) return hr; return hr; } /* Send Reply only with the Reply Code*/ HRESULT CSocksConnection::SendReply(BYTE ReplyCode) { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; // Get the pointer and size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) return hr; SOCKS5Request *Rep = (SOCKS5Request*)pBuffer; Rep->VER = 5; Rep->CMD = ReplyCode; Rep->RES = 0; hr = m_spCmdBuffer->SetDataSize(sizeof(*Rep)); if (FAILED(hr)) { return hr; } hr = m_spClientSocket->Send(m_spCmdBuffer, this, ReplyCode == CONN_SUCC ? eSendSuccess : eSendError); return hr; } // Send first replay to the client HRESULT CSocksConnection::SendFirstOrSecReply(BYTE ReplyCode,BOOL fFirst) { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; // Get the pointer and size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) { return hr; } SOCKS5First *Rep = (SOCKS5First*)pBuffer; Rep->VER = 0x05; if (ReplyCode == 0xFF){ Rep->ANS = 0xFF; } else if (fFirst) { Rep->ANS = 0x02; } else { Rep->ANS = 0x00; } hr = m_spCmdBuffer->SetDataSize(sizeof(*Rep)); if (FAILED(hr)) { return hr; } if (fFirst) { hr = m_spClientSocket->Send(m_spCmdBuffer, this, ReplyCode == 0xFF ? eSendError : eSendAnsToProposal); } else { hr = m_spClientSocket->Send(m_spCmdBuffer, this, ReplyCode == 0xFF ? eSendError : eSendAnsToNegotiation); } return hr; } /* Send Reply with the Reply Code and an address*/ HRESULT CSocksConnection::SendReplyB(BYTE ReplyCode, SOCKADDR Addr ,BOOL fFirst) { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; // Get the pointer and size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) { return hr; } SOCKS5Answer *Rep = (SOCKS5Answer*)pBuffer; Rep->VER = 5; Rep->REP = ReplyCode; Rep->RES = 0; Rep->ATYP = 0x01; Rep->port = LPSOCKADDR_IN(&Addr)->sin_port; Rep->ip = LPSOCKADDR_IN(&Addr)->sin_addr.s_addr; hr = m_spCmdBuffer->SetDataSize(sizeof(SOCKS5Answer)); if (FAILED(hr)) { return hr; } if (fFirst) { hr = m_spClientSocket->Send(m_spCmdBuffer, this, ReplyCode == CONN_SUCC ? eFirstSuccess : eSendError); } else { hr = m_spClientSocket->Send(m_spCmdBuffer, this, ReplyCode == CONN_SUCC ? eSendSuccess : eSendError); } return hr; } HRESULT CSocksConnection::HandleProposal() { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; DWORD index; // Get the pointer and size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) return hr; // Check for overflow. if (dwBufferSize == MAX_CMD_LENGTH) { return SendFirstOrSecReply(0xFF,TRUE); } // Check for closed connection (0 bytes received) if (dwBufferSize == m_dwLastBufferSize) { Abort(); return S_OK; } m_dwLastBufferSize = dwBufferSize; // // First byte is the version. See if we support it // if (pBuffer[0] != 5) { return SendFirstOrSecReply(NO_ACCEPT_METHOD,TRUE); } // The request should include at least 3 byte the ver the number // and at least one request. if (dwBufferSize < 3) { return m_spClientSocket->Recv(m_spCmdBuffer, this, eGetProposal); } DWORD NumOfProposals = *(pBuffer+1); if (dwBufferSize-2 < NumOfProposals) { return m_spClientSocket->Recv(m_spCmdBuffer, this, eGetProposal); } // loop over the Proposals for (index=1;index<= NumOfProposals;index++) { if ( *(pBuffer+1+index) == 2 ) { return SendFirstOrSecReply(CONN_SUCC,TRUE); } } return SendFirstOrSecReply(NO_ACCEPT_METHOD,TRUE); } // Check the User and Password sent by the client HRESULT CSocksConnection::HandleNegotiation() { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; // Get the pointer to the buffer and the size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) { return hr; } // Check for overflow. if (dwBufferSize == MAX_CMD_LENGTH) { return SendFirstOrSecReply(NO_ACCEPT_METHOD,FALSE); } // Check for closed connection (0 bytes received) if (dwBufferSize == m_dwLastBufferSize) { Abort(); return S_OK; } m_dwLastBufferSize = dwBufferSize; // // First byte is the version. See if we support it // if (pBuffer[0] != 0x01) { return SendFirstOrSecReply(NO_ACCEPT_METHOD,FALSE); } // The request should include at least 3 byte the ver the number // and at least one request. if (dwBufferSize < 2) { return m_spClientSocket->Recv(m_spCmdBuffer, this, eGetNegotiation); } DWORD ULen = *(pBuffer+1); // User name too long, overflow if (ULen >= MAX_USR_LEN) { return SendFirstOrSecReply(NO_ACCEPT_METHOD,FALSE); } // plus one for the password len if (dwBufferSize-2 < ULen+1) { return m_spClientSocket->Recv(m_spCmdBuffer, this, eGetNegotiation); } // Password len is in location = start + 1 byte(ver)+ 1 byte (user len) + Usrname DWORD PLen = *(pBuffer + 2 + ULen) ; // Password too long, overflow if (PLen >= MAX_PWD_LEN) { return SendFirstOrSecReply(NO_ACCEPT_METHOD,FALSE); } // The size minus ver byte , minus ulen byte, minus user data // and minus password len (1 byte). if (dwBufferSize - 2 - ULen -1 < PLen) { return m_spClientSocket->Recv(m_spCmdBuffer, this, eGetNegotiation); } //finding domain length DWORD DLen; for(DLen = 2; DLen < ULen; DLen++) { if (pBuffer[DLen] == '\\') { break; } } if (DLen == ULen) // no domain specified (local account) { DLen = 1; strcpy(m_domain, "."); strncpy(m_user,(char *)(pBuffer+2), ULen); m_user[ULen] = '\0'; strncpy(m_pwd,(char *)(pBuffer+2+ULen+1), PLen); // +2 for the version and username length field, +1 for the password length field m_pwd[PLen] = '\0'; } else { DLen -= 2; // the first 2 bytes of the SOCKS message aren't part of the domain ULen -= (DLen + 1); // the domain and the backslash aren't part of the username strncpy(m_domain,(char *)(pBuffer+2), DLen); m_domain[DLen] = '\0'; strncpy(m_user,(char *)(pBuffer+2+DLen+1), ULen); m_user[ULen] = '\0'; strncpy(m_pwd,(char *)(pBuffer+2+ULen+1+DLen+1), PLen); m_pwd[PLen] = '\0'; } m_spWspProxy->StartHeavyBlockingOperation(); BOOL LogOnResault = LogonUserA( m_user, m_domain, m_pwd, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &m_token); m_spWspProxy->EndHeavyBlockingOperation(); // Authentication by user name and password if (LogOnResault) { return SendFirstOrSecReply(CONN_SUCC,FALSE); } else { DEBUGPRINT(("The user is not Authorized. use: %s password: %s Error num: %d", m_user, m_pwd, GetLastError())); return SendFirstOrSecReply(NO_ACCEPT_METHOD,FALSE); } } // Handle a command - Connect or Bind HRESULT CSocksConnection::CommandHandler() { BYTE *pBuffer; DWORD dwBufferSize; HRESULT hr; SOCKADDR ProxyAddr; SOCKADDR ClientAddr; SOCKADDR socksExternalAddr; SOCKADDR SendAddr; int lenS; int len ; int lenC ; int lenE ; char name[MAX_CMD_LENGTH]; DWORD IP; USHORT port; Lock(); CComPtr spClientSocket = m_spClientSocket; Unlock(); if (spClientSocket == NULL) { // the client already closed the connection return S_OK; } // Get the pointer and size; hr = m_spCmdBuffer->GetBufferAndSize(&pBuffer, &dwBufferSize); if (FAILED(hr)) { return hr; } // Check for overflow. if (dwBufferSize >= MAX_CMD_LENGTH) { return SendReply(CONN_FAIL); } // Check for closed connection (0 bytes received) if (dwBufferSize == m_dwLastBufferSize) { Abort(); return S_OK; } m_dwLastBufferSize = dwBufferSize; // // First byte is the version. See if we support it // if (pBuffer[0] != 5) { return SendReply(CONN_FAIL); } if (dwBufferSize <= 4 ) { return spClientSocket->Recv(m_spCmdBuffer, this, eGetCmd); } SOCKS5Request *Req = (SOCKS5Request*)pBuffer; // check if we got all data : const size part + IP +port(2 bytes) // We should get 4 bytes : Ver,Cmd,Rsv,type plus name len // and the name itself and the port (2 more bytes). if (Req->ATYP == 0x03 ) { if (dwBufferSize < 5 + (DWORD)Req->Addr.LEN + 2 ) { return spClientSocket->Recv(m_spCmdBuffer, this, eGetCmd); } } else { // the type is '01' - The IP addr is of 4 bytes. if (dwBufferSize < 10 ) { return spClientSocket->Recv(m_spCmdBuffer, this, eGetCmd); } } /*************************************************************/ /* handle name resulotion */ /*************************************************************/ if (Req->ATYP == 0x03 ) { /* Name resulotion is needed */ // copy the domain name // no buffer-overrun possible because we required Req->Addr.LEN characters // to be received by the socket for the domain name (and limited the entire // received data to MAX_CMD_LEN) strncpy(name, ((char*)&Req->Addr) + 1, Req->Addr.LEN); name[Req->Addr.LEN] = '\0'; // Get the IP from this name m_spWspProxy->StartHeavyBlockingOperation(); HOSTENT *HostDetails = gethostbyname(name); m_spWspProxy->EndHeavyBlockingOperation(); if (!HostDetails) { return SendReply(CONN_FAIL); } IP = *(DWORD*)(HostDetails->h_addr_list[0]); port = *(USHORT *)((BYTE *)Req + 5 + Req->Addr.LEN); } else { IP = Req->Addr.IpAndPort.ip; port = Req->Addr.IpAndPort.port; } // Connect Command if (Req->CMD == CONN_CMD) { SOCKADDR_IN DestinationAddress; DestinationAddress.sin_family = AF_INET; DestinationAddress.sin_port = port; DestinationAddress.sin_addr.s_addr = IP; // Locate appropriate session // Currently use NAT CComPtr spSession; len = sizeof(ProxyAddr); lenC = sizeof(ClientAddr); lenS = sizeof(SendAddr); spClientSocket->GetSockName(&ProxyAddr, &len); spClientSocket->GetPeerName(&ClientAddr, &lenC); u_long ulIp = LPSOCKADDR_IN(&ClientAddr)->sin_addr.s_addr; hr = m_pSocksfilter->FindOrCreateSession( ulIp, m_user, m_domain, ClientAddr, ProxyAddr, &spSession, m_token ); if (FAILED(hr)) { return SendReply(CONN_FAIL); } // Create socket hr = spSession->CreateNetworkSocket(FWX_PROTOCOL_TCP, &m_spExternalSocket); if (FAILED(hr)) { return SendReply(CONN_FAIL); } // add the socket to the list hr = m_pSocksfilter->InsertSocket(ulIp,m_user,m_spExternalSocket); // make a connection hr = m_spExternalSocket->Connect( (LPSOCKADDR)&DestinationAddress, sizeof(DestinationAddress)); if (FAILED(hr)) { return SendReply(CONN_FAIL); } m_spExternalSocket->GetSockName(&SendAddr,&lenS); return SendReplyB(CONN_SUCC,SendAddr,FALSE); } else if (Req->CMD == BIND_CMD) { // Bind Command SOCKADDR_IN BindAddress; BindAddress.sin_family = AF_INET; BindAddress.sin_port = 0; BindAddress.sin_addr.s_addr = 0; CComPtr spSession; len = sizeof(ProxyAddr); lenC = sizeof(ClientAddr); spClientSocket->GetSockName(&ProxyAddr, &len); spClientSocket->GetPeerName(&ClientAddr, &lenC); u_long ulIp = LPSOCKADDR_IN(&ClientAddr)->sin_addr.s_addr; hr = m_pSocksfilter->FindSession(ulIp,m_user,&spSession); if (FAILED(hr)) { return SendReply(CONN_FAIL); } // Create socket hr = spSession->CreateNetworkSocket(FWX_PROTOCOL_TCP, &m_spExternalSocket); if (FAILED(hr)) { return SendReply(CONN_FAIL); } // Try to locate the external card IP on the proxy CComPtr socket; hr = m_pSocksfilter->findSocket(ulIp,m_user,IP,port,&socket); // NOT FOUND THAT socket if (socket == NULL) { return SendReply(CONN_FAIL); } lenE = sizeof(socksExternalAddr); socket->GetSockName(&socksExternalAddr,&lenE); // The Bind Address refers to the external address of the socket BindAddress.sin_addr.s_addr = LPSOCKADDR_IN(&socksExternalAddr)->sin_addr.s_addr ; // Make a Bind for the client hr = m_spExternalSocket->Bind( (LPSOCKADDR)&BindAddress, sizeof(BindAddress), NULL ); if (FAILED(hr)) { return SendReply(CONN_FAIL); } lenS = sizeof(SendAddr); m_spExternalSocket->GetSockName(&SendAddr,&lenS); // Listen to incoming connections hr = m_spExternalSocket->Listen(5); if (FAILED(hr)) { return SendReply(CONN_FAIL); } // Accept a connection hr = m_spExternalSocket->Accept(this,0); if (FAILED(hr)) { return SendReply(CONN_FAIL); } return SendReplyB(CONN_SUCC,SendAddr,TRUE); } else { // Command not supported return SendReply(CONN_FAIL); } } void CSocksConnection::CloseSockets(BOOLEAN fAbovtive) { HRESULT hr; SOCKADDR ClientAddr; int lenC; SessionItem oldSession; CComPtr spClientSocket; CComPtr spExternalSocket; Lock(); spClientSocket.Attach(m_spClientSocket.Detach()); spExternalSocket.Attach(m_spExternalSocket.Detach()); Unlock(); // if this socket wasn't closed already if (spClientSocket != NULL) { // erease from map lenC = sizeof(ClientAddr); spClientSocket->GetPeerName(&ClientAddr, &lenC); u_long ulIp = LPSOCKADDR_IN(&ClientAddr)->sin_addr.s_addr; IT it; it = m_pSocksfilter->findInMap(ulIp, m_user); if (it == NULL) { // intermnal error - should have been here DEBUGPRINT(("Close Socket - the ip not in map\n")); } else { // sub from ref count delete if need hr = m_pSocksfilter->SubCount(&(*it).second); oldSession = (*it).second; //--oldSession.Count; if (oldSession.Count == 0) { m_pSocksfilter->DeleteFromMap(it); } else // No need to delete just update the counter { // delete from list SOCKLIST::iterator i; for (i = oldSession.socklist.begin(); i != oldSession.socklist.end(); ++i) { if (*i == spExternalSocket) { m_pSocksfilter->DeleteFromList(&oldSession.socklist,i); break; } } } } hr = spClientSocket->Close(fAbovtive); _ASSERT(SUCCEEDED(hr)); } if (spExternalSocket) { hr = spExternalSocket->Close(fAbovtive); _ASSERT(SUCCEEDED(hr)); } }