#include "NativeFeatureIncludes.h" #if _RAKNET_SUPPORT_DynDNS==1 && _RAKNET_SUPPORT_TCPInterface==1 #include "TCPInterface.h" #include "SocketLayer.h" #include "DynDNS.h" #include "GetTime.h" using namespace RakNet; struct DynDnsResult { const char *description; const char *code; DynDnsResultCode resultCode; }; DynDnsResult resultTable[13] = { // See http://www.dyndns.com/developers/specs/flow.pdf {"DNS update success.\nPlease wait up to 60 seconds for the change to take effect.\n", "good", RC_SUCCESS}, // Even with success, it takes time for the cache to update! {"No change", "nochg", RC_NO_CHANGE}, {"Host has been blocked. You will need to contact DynDNS to reenable.", "abuse", RC_ABUSE}, {"Useragent is blocked", "badagent", RC_BAD_AGENT}, {"Username/password pair bad", "badauth", RC_BAD_AUTH}, {"Bad system parameter", "badsys", RC_BAD_SYS}, {"DNS inconsistency", "dnserr", RC_DNS_ERROR}, {"Paid account feature", "!donator", RC_NOT_DONATOR}, {"No such host in system", "nohost", RC_NO_HOST}, {"Invalid hostname format", "notfqdn", RC_NOT_FQDN}, {"Serious error", "numhost", RC_NUM_HOST}, {"This host exists, but does not belong to you", "!yours", RC_NOT_YOURS}, {"911", "911", RC_911}, }; DynDNS::DynDNS() { connectPhase=CP_IDLE; tcp=0; } DynDNS::~DynDNS() { if (tcp) RakNet::OP_DELETE(tcp, _FILE_AND_LINE_); } void DynDNS::Stop(void) { tcp->Stop(); connectPhase = CP_IDLE; RakNet::OP_DELETE(tcp, _FILE_AND_LINE_); tcp=0; } // newIPAddress is optional - if left out, DynDNS will use whatever it receives void DynDNS::UpdateHostIP(const char *dnsHost, const char *newIPAddress, const char *usernameAndPassword ) { myIPStr[0]=0; if (tcp==0) tcp = RakNet::OP_NEW(_FILE_AND_LINE_); connectPhase = CP_IDLE; host = dnsHost; if (tcp->Start(0, 1)==false) { SetCompleted(RC_TCP_FAILED_TO_START, "TCP failed to start"); return; } connectPhase = CP_CONNECTING_TO_CHECKIP; tcp->Connect("checkip.dyndns.org", 80, false); // See https://www.dyndns.com/developers/specs/syntax.html getString="GET /nic/update?hostname="; getString+=dnsHost; if (newIPAddress) { getString+="&myip="; getString+=newIPAddress; } getString+="&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG HTTP/1.0\n"; getString+="Host: members.dyndns.org\n"; getString+="Authorization: Basic "; char outputData[512]; TCPInterface::Base64Encoding(usernameAndPassword, (int) strlen(usernameAndPassword), outputData); getString+=outputData; getString+="User-Agent: Jenkins Software LLC - PC - 1.0\n\n"; } void DynDNS::Update(void) { if (connectPhase==CP_IDLE) return; serverAddress=tcp->HasFailedConnectionAttempt(); if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS) { SetCompleted(RC_TCP_DID_NOT_CONNECT, "Could not connect to DynDNS"); return; } serverAddress=tcp->HasCompletedConnectionAttempt(); if (serverAddress!=UNASSIGNED_SYSTEM_ADDRESS) { if (connectPhase == CP_CONNECTING_TO_CHECKIP) { checkIpAddress=serverAddress; connectPhase = CP_WAITING_FOR_CHECKIP_RESPONSE; tcp->Send("GET\n\n", (unsigned int) strlen("GET\n\n"), serverAddress, false); // Needs 2 newlines! This is not documented and wasted a lot of my time } else { connectPhase = CP_WAITING_FOR_DYNDNS_RESPONSE; tcp->Send(getString.C_String(), (unsigned int) getString.GetLength(), serverAddress, false); } phaseTimeout=RakNet::GetTime()+1000; } if (connectPhase==CP_WAITING_FOR_CHECKIP_RESPONSE && RakNet::GetTime()>phaseTimeout) { connectPhase = CP_CONNECTING_TO_DYNDNS; tcp->CloseConnection(checkIpAddress); tcp->Connect("members.dyndns.org", 80, false); } else if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE && RakNet::GetTime()>phaseTimeout) { SetCompleted(RC_DYNDNS_TIMEOUT, "DynDNS did not respond"); return; } Packet *packet = tcp->Receive(); if (packet) { if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE) { unsigned int i; char *result; result=strstr((char*) packet->data, "Connection: close"); if (result!=0) { result+=strlen("Connection: close"); while (*result && (*result=='\r') || (*result=='\n') || (*result==' ') ) result++; for (i=0; i < 13; i++) { if (strncmp(resultTable[i].code, result, strlen(resultTable[i].code))==0) { if (resultTable[i].resultCode==RC_SUCCESS) { // Read my external IP into myIPStr // Advance until we hit a number while (*result && ((*result<'0') || (*result>'9')) ) result++; if (*result) { SystemAddress parser; parser.FromString(result); parser.ToString(false, myIPStr); } } tcp->DeallocatePacket(packet); SetCompleted(resultTable[i].resultCode, resultTable[i].description); break; } } if (i==13) { tcp->DeallocatePacket(packet); SetCompleted(RC_UNKNOWN_RESULT, "DynDNS returned unknown result"); } } else { tcp->DeallocatePacket(packet); SetCompleted(RC_PARSING_FAILURE, "Parsing failure on returned string from DynDNS"); } return; } else { /* HTTP/1.1 200 OK Content-Type: text/html Server: DynDNS-CheckIP/1.0 Connection: close Cache-Control: no-cache Pragma: no-cache Content-Length: 105 Current IP CheckCurrent IP Address: 98.1 89.219.22 Connection to host lost. */ char *result; result=strstr((char*) packet->data, "Current IP Address: "); if (result!=0) { result+=strlen("Current IP Address: "); SystemAddress myIp; myIp.FromString(result); myIp.ToString(false, myIPStr); // Resolve DNS we are setting. If equal to current then abort const char *existingHost = ( char* ) SocketLayer::DomainNameToIP( host.C_String() ); if (existingHost && strcmp(existingHost, myIPStr)==0) { // DynDNS considers setting the IP to what it is already set abuse tcp->DeallocatePacket(packet); SetCompleted(RC_DNS_ALREADY_SET, "No action needed"); return; } } tcp->DeallocatePacket(packet); tcp->CloseConnection(packet->systemAddress); connectPhase = CP_CONNECTING_TO_DYNDNS; tcp->Connect("members.dyndns.org", 80, false); } } if (tcp->HasLostConnection()!=UNASSIGNED_SYSTEM_ADDRESS) { if (connectPhase==CP_WAITING_FOR_DYNDNS_RESPONSE) { SetCompleted(RC_CONNECTION_LOST_WITHOUT_RESPONSE, "Connection lost to DynDNS during GET operation"); } } } #endif // _RAKNET_SUPPORT_DynDNS