NS/releases/3.04/source/mod/AvHCurl.cpp

546 lines
14 KiB
C++
Raw Normal View History

//======== (C) Copyright 2002 Charles G. Cleveland All rights reserved. =========
//
// The copyright to the contents herein is the property of Charles G. Cleveland.
// The contents may be used and/or copied only with the written permission of
// Charles G. Cleveland, or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose:
//
// $Workfile: AvHCurl.cpp $
// $Date: $
//
//-------------------------------------------------------------------------------
// $Log: $
//===============================================================================
#include "build.h"
#if !defined(USE_UPP) // forget entire file if we're using UPP instead
#include "util/nowarnings.h"
#include "extdll.h"
#include "dlls/util.h"
#include "util/Tokenizer.h"
#include "mod/AvHGamerules.h"
#include "mod/AvHServerUtil.h"
#include "curl/include/curl/curl.h"
extern cvar_t avh_serverops;
extern unsigned int gTimeLastUpdatedUplink;
extern AuthMaskListType gAuthMaskList;
extern const char* kSteamIDPending;
extern const char* kSteamIDPrefix;
//const char* kWebStatusURL = "https://www.natural-selection.org/cgi-bin/VictoryStats.pl";
//const char* kWebStatusURL = "http://www.natural-selection.org/cgi-bin/ikonboard/ikonboard.cgi";
void AvHGamerules::PostVictoryStatsToWeb(const string& inFormParams) const
{
// CURL* curl;
// CURLcode res;
//
// curl = curl_easy_init();
// if(curl)
// {
// /* First set the URL that is about to receive our POST. This URL can
// just as well be a https:// URL if that is what should receive the
// data. */
// curl_easy_setopt(curl, CURLOPT_URL, kWebStatusURL);
//
// /* Now specify the POST data */
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, inFormParams.c_str());
//
// /* Perform the request, res will get the return code */
// res = curl_easy_perform(curl);
//
// /* always cleanup */
// curl_easy_cleanup(curl);
// }
//struct soap soap; // allocate gSOAP runtime environment
//soap;
/*
char *sym;
float q;
if (argc <= 1)
sym = "IBM";
else
sym = argv[1];
soap_init(&soap); // must initialize
if (soap_call_ns__getQuote(&soap, "http://services.xmethods.net/soap", "", sym, &q) == 0)
printf("\nCompany - %s Quote - %f\n", sym, q);
else
{
soap_print_fault(&soap, stderr);
soap_print_fault_location(&soap, stderr);
}
*/
}
// This authID could be either a WONID or a STEAMID
int AvHGamerules::GetAuthenticationMask(const string& inAuthID) const
{
// Iterate through each mask type, oring it into the mask if id is found
int theMask = 0;
if(inAuthID == kSteamIDPending)
{
theMask = PLAYERAUTH_PENDING;
}
else
{
// Error check the incoming authID
if(AvHSUGetIsValidAuthID(inAuthID))
{
for(AuthMaskListType::const_iterator theIter = gAuthMaskList.begin(); theIter != gAuthMaskList.end(); theIter++)
{
const AuthIDListType& theAuthIDList = theIter->second;
for(AuthIDListType::const_iterator theAuthListIter = theAuthIDList.begin(); theAuthListIter != theAuthIDList.end(); theAuthListIter++)
{
// Check if incoming authID is equal to this record's WONID _or_ STEAMID. This is kind of sloppy, but it allows backwards compatibility.
if((theAuthListIter->first == inAuthID) || (theAuthListIter->second == inAuthID))
{
AvHPlayerAuthentication theCurrentAuthMask = theIter->first;
theMask |= theCurrentAuthMask;
break;
}
}
}
// Check if any players are server ops
const AuthIDListType& theServerOps = this->GetServerOpList();
for(AuthIDListType::const_iterator theAuthListIter = theServerOps.begin(); theAuthListIter != theServerOps.end(); theAuthListIter++)
{
if((inAuthID == theAuthListIter->first) || (inAuthID == theAuthListIter->second))
{
theMask |= PLAYERAUTH_SERVEROP;
break;
}
}
}
}
return theMask;
}
void AvHGamerules::AddAuthStatus(AvHPlayerAuthentication inAuthMask, const string& inWONID, const string& inSteamID)
{
// int theData = 0;
// Not sure why this is needed, but it seems to ("warning, decorated name length exceeded)
#pragma warning (disable: 4503)
AuthIDListType& theAuthList = gAuthMaskList[inAuthMask];
bool theFoundEntry = false;
for(AuthIDListType::iterator theIter = theAuthList.begin(); theIter != theAuthList.end(); theIter++)
{
// Allow duplicate WONids but not SteamIDs
if(inSteamID == theIter->second)
{
theFoundEntry = true;
break;
}
}
if(!theFoundEntry)
{
// Add entry for this id
theAuthList.push_back( make_pair(inWONID, inSteamID) );
}
}
struct MemoryStruct {
char *memory;
size_t size;
};
size_t WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data)
{
register int realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)data;
mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
if (mem->memory) {
memcpy(&(mem->memory[mem->size]), ptr, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
}
return realsize;
}
bool TagToAuthMask(const string& inTag, AvHPlayerAuthentication& outMask)
{
bool theSuccess = false;
if(inTag == "dev")
{
outMask = PLAYERAUTH_DEVELOPER;
theSuccess = true;
}
else if(inTag == "guide")
{
outMask = PLAYERAUTH_GUIDE;
theSuccess = true;
}
else if(inTag == "op")
{
outMask = PLAYERAUTH_SERVEROP;
theSuccess = true;
}
else if(inTag == "pt")
{
outMask = PLAYERAUTH_PLAYTESTER;
theSuccess = true;
}
else if(inTag == "const")
{
outMask = PLAYERAUTH_CONTRIBUTOR;
theSuccess = true;
}
else if(inTag == "vet")
{
outMask = PLAYERAUTH_VETERAN;
theSuccess = true;
}
else if(inTag == "betaop")
{
outMask = PLAYERAUTH_BETASERVEROP;
theSuccess = true;
}
return theSuccess;
}
// Build string in obfuscated procedural way so it can't be scanned for and hacked. Build URL using IP instead of domain to
// make sure proxy can't change it either.
string BuildAuthenticationURL()
{
// "http://www.natural-selection.org/auth.php";
// (backwards-compatibly for pre-2.1 servers use: (http://www.natural-selection.org/auth/)
string kAuthURL = "h";
kAuthURL += "t";
kAuthURL += "t";
kAuthURL += "p";
//kAuthURL += "s";
kAuthURL += ":";
kAuthURL += "/";
kAuthURL += "/";
kAuthURL += "w";
kAuthURL += "w";
kAuthURL += "w";
kAuthURL += ".";
kAuthURL += "n";
kAuthURL += "a";
kAuthURL += "t";
kAuthURL += "u";
kAuthURL += "r";
kAuthURL += "a";
kAuthURL += "l";
kAuthURL += "-";
kAuthURL += "s";
kAuthURL += "e";
kAuthURL += "l";
kAuthURL += "e";
kAuthURL += "c";
kAuthURL += "t";
kAuthURL += "i";
kAuthURL += "o";
kAuthURL += "n";
kAuthURL += ".";
kAuthURL += "o";
kAuthURL += "r";
kAuthURL += "g";
kAuthURL += "/";
kAuthURL += "a";
kAuthURL += "u";
kAuthURL += "t";
kAuthURL += "h";
kAuthURL += "/";
kAuthURL += "a";
kAuthURL += "u";
kAuthURL += "t";
kAuthURL += "h";
kAuthURL += ".";
kAuthURL += "p";
kAuthURL += "h";
kAuthURL += "p";
return kAuthURL;
}
string BuildVersionURL()
{
// "http://207.44.144.68/auth.txt";
string kAuthURL = "h";
kAuthURL += "t";
kAuthURL += "t";
kAuthURL += "p";
//kAuthURL += "s";
kAuthURL += ":";
kAuthURL += "/";
kAuthURL += "/";
kAuthURL += "w";
kAuthURL += "w";
kAuthURL += "w";
kAuthURL += ".";
kAuthURL += "n";
kAuthURL += "a";
kAuthURL += "t";
kAuthURL += "u";
kAuthURL += "r";
kAuthURL += "a";
kAuthURL += "l";
kAuthURL += "-";
kAuthURL += "s";
kAuthURL += "e";
kAuthURL += "l";
kAuthURL += "e";
kAuthURL += "c";
kAuthURL += "t";
kAuthURL += "i";
kAuthURL += "o";
kAuthURL += "n";
kAuthURL += ".";
kAuthURL += "o";
kAuthURL += "r";
kAuthURL += "g";
kAuthURL += "/";
kAuthURL += "a";
kAuthURL += "u";
kAuthURL += "t";
kAuthURL += "h";
kAuthURL += "/";
kAuthURL += "v";
kAuthURL += "e";
kAuthURL += "r";
kAuthURL += "s";
kAuthURL += "i";
kAuthURL += "o";
kAuthURL += "n";
kAuthURL += ".";
kAuthURL += "t";
kAuthURL += "x";
kAuthURL += "t";
return kAuthURL;
}
string BuildUserPassword()
{
string theUserPassword;
// auth:mnbv5tgb
theUserPassword += "a";
theUserPassword += "u";
theUserPassword += "t";
theUserPassword += "h";
theUserPassword += ":";
theUserPassword += "m";
theUserPassword += "n";
theUserPassword += "b";
theUserPassword += "v";
theUserPassword += "5";
theUserPassword += "t";
theUserPassword += "g";
theUserPassword += "b";
return theUserPassword;
}
CURLcode PopulateChunkFromURL(const string& inURL, MemoryStruct& outChunk)
{
const int kCurlTimeout = 5;
// init the curl session
CURL* theCurlHandle = curl_easy_init();
// specify URL to get
curl_easy_setopt(theCurlHandle, CURLOPT_URL, inURL.c_str());
// send all data to this function
curl_easy_setopt(theCurlHandle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
// Ignore header and progress so there isn't extra stuff at the beginning of the data returned
//curl_easy_setopt(theCurlHandle, CURLOPT_HEADER, 0);
//curl_easy_setopt(theCurlHandle, CURLOPT_NOPROGRESS, 1);
//curl_easy_setopt(theCurlHandle, CURLOPT_VERBOSE, 0);
//curl_easy_setopt(theCurlHandle, CURLOPT_NOBODY, 1);
// timeout (3 seconds)
curl_easy_setopt(theCurlHandle, CURLOPT_CONNECTTIMEOUT, kCurlTimeout);
curl_easy_setopt(theCurlHandle, CURLOPT_TIMEOUT, kCurlTimeout);
curl_easy_setopt(theCurlHandle, CURLOPT_NOSIGNAL, true);
// CURLOPT_LOW_SPEED_LIMIT?
// CURLOPT_LOW_SPEED_TIME?
// Specify the user name and password
string kUserPassword = BuildUserPassword();
curl_easy_setopt(theCurlHandle, CURLOPT_USERPWD, kUserPassword.c_str());
// we pass our 'theChunk' struct to the callback function
outChunk.memory=NULL; /* we expect realloc(NULL, size) to work */
outChunk.size = 0; /* no data at this point */
curl_easy_setopt(theCurlHandle, CURLOPT_FILE, (void *)&outChunk);
// get it!
CURLcode theCode = curl_easy_perform(theCurlHandle);
// cleanup curl stuff
curl_easy_cleanup(theCurlHandle);
return theCode;
}
void AvHGamerules::InitializeAuthentication()
{
bool theSuccess = false;
ALERT(at_console, "\tReading authentication data...");
struct MemoryStruct theChunk;
CURLcode theCode = PopulateChunkFromURL(BuildAuthenticationURL(), theChunk);
// Now parse data and add users for each mask
if((theCode == CURLE_OK) && theChunk.memory)
{
// Clear existing authentication data, only after lookup succeeds
gAuthMaskList.clear();
string theString(theChunk.memory);
string theDelimiters("\r\n");
StringVector theLines;
Tokenizer::split(theString, theDelimiters, theLines);
// Run through each line
int theNumConstellationMembers = 0;
for(StringVector::const_iterator theIter = theLines.begin(); theIter != theLines.end(); theIter++)
{
// If it's not empty and not a comment
char theFirstChar = (*theIter)[0];
if((theFirstChar != '/') && (theFirstChar != '#'))
{
// Split up tag and number
StringVector theTokens;
if(Tokenizer::split(*theIter, " ", theTokens) == 3)
{
// Translate tag to auth mask
string theTag = theTokens[0];
string theWONID = theTokens[1];
string theSteamID = theTokens[2];
// Upper-case prefix
UppercaseString(theSteamID);
// Make sure it starts with "STEAM_X:Y"
if(strncmp(theSteamID.c_str(), kSteamIDPrefix, strlen(kSteamIDPrefix)))
{
string theNewSteamID = kSteamIDPrefix + theSteamID;
theSteamID = theNewSteamID;
}
// Add auth status
AvHPlayerAuthentication theMask = PLAYERAUTH_NONE;
if(TagToAuthMask(theTag, theMask))
{
// Count Constellation members fyi
if(theMask & PLAYERAUTH_CONTRIBUTOR)
{
theNumConstellationMembers++;
}
if((theWONID != "") || (theSteamID != ""))
{
#ifdef DEBUG
char theMessage[512];
sprintf(theMessage, " Adding auth mask: %s %s %s\n", theTag.c_str(), theWONID.c_str(), theSteamID.c_str());
ALERT(at_logged, theMessage);
#endif
this->AddAuthStatus(theMask, theWONID, theSteamID);
theSuccess = true;
}
}
}
}
}
// Breakpoint to see how many Constellation members there are (don't even think of printing or logging)
int a = 0;
}
else
{
int a = 0;
}
// Now build server op list
this->mServerOpList.clear();
// Parse contents server op variable
string theServerOpsString(avh_serverops.string);
// Tokenize string
StringVector theServerOpsStrings;
Tokenizer::split(theServerOpsString, ";", theServerOpsStrings);
// For each in list, add as an int to our list
for(StringVector::const_iterator theIter = theServerOpsStrings.begin(); theIter != theServerOpsStrings.end(); theIter++)
{
// Add whatever the put in this line as both the WONID and SteamID
string theCurrentAuthID = *theIter;
this->mServerOpList.push_back( make_pair(theCurrentAuthID, theCurrentAuthID));
}
if(theSuccess)
{
ALERT(at_console, "success.\n");
}
else
{
ALERT(at_console, "failure.\n");
}
gTimeLastUpdatedUplink = AvHSUTimeGetTime();
}
void AvHGamerules::DisplayVersioning()
{
struct MemoryStruct theChunk;
CURLcode theCode = PopulateChunkFromURL(BuildVersionURL(), theChunk);
// Now parse data and add users for each mask
if((theCode == CURLE_OK) && theChunk.memory)
{
string theString(theChunk.memory, theChunk.size);
string theDelimiters("\r\n");
StringVector theLines;
Tokenizer::split(theString, theDelimiters, theLines);
if(theLines.size() > 0)
{
string theCurrentVersion(theLines[0]);
string theCurrentGameVersion = AvHSUGetGameVersionString();
char theMessage[1024];
if(theCurrentGameVersion != theCurrentVersion)
{
sprintf(theMessage, "\tServer out of date. NS version %s is now available!\n", theCurrentVersion.c_str());
ALERT(at_console, theMessage);
}
else
{
sprintf(theMessage, "\tServer version is up to date.\n");
ALERT(at_console, theMessage);
}
}
}
}
#endif //!defined(USE_UPP)