Files
SRB2Kart-Saturn/src/http-mserv.c
T
alufolie91 16577877d3 replace all sprintf with snprintf
some of this was taken from https://gitlab.com/kart-krew-dev/ring-racers/-/commit/59f23dfa181327ed5c00f55688840d39e3583569 so thx!
should fix apple clang warnings and is alot safer to have all this crap be bounded
theres prob alot more other stuff, and id love to eventually replace all the strcpy family functions with snprintf and equivalent
2026-08-09 17:49:31 +02:00

640 lines
11 KiB
C

// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2020 by James R.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
// \brief HTTP based master server
/*
Documentation available here.
<https://ms.kartkrew.org/tools/api/2.2/>
*/
#ifdef HAVE_CURL
#include <curl/curl.h>
#include "m_curl.h"
#endif
#include "doomdef.h"
#include "d_clisrv.h"
#include "command.h"
#include "console.h"
#include "m_argv.h"
#include "m_menu.h"
#include "mserv.h"
#include "i_tcp.h"/* for current_port */
#include "i_threads.h"
/* reasonable default I guess?? */
#define DEFAULT_BUFFER_SIZE (4096)
/* I just stop myself from making macros anymore. */
#define Blame( ... ) \
CONS_Printf("\x85" __VA_ARGS__)
static void MasterServer_Debug_OnChange (void);
consvar_t cv_masterserver_timeout = {
"masterserver_timeout", "5", CV_SAVE, CV_Unsigned,
NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
};
CV_PossibleValue_t masterserver_debug_cons_t[] = {{0, "Off"}, {1, "PrintOnly"}, {2, "On"}, {0, NULL}};
consvar_t cv_masterserver_debug = {
"masterserver_debug", "Off", CV_SAVE|CV_CALL, masterserver_debug_cons_t,
MasterServer_Debug_OnChange, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
};
consvar_t cv_masterserver_token = {
"masterserver_token", "", CV_SAVE, NULL,
NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
};
#define HMS_QUERY_VERSION "?v=2.2"
#ifdef MASTERSERVER
static int hms_started;
static char *hms_api;
#ifdef HAVE_THREADS
static I_mutex hms_api_mutex;
#endif
static char *hms_server_token;
struct HMS_buffer
{
CURL *curl;
char *buffer;
int needle;
int end;
char *errbuf;
};
static void
Contact_error (void)
{
CONS_Alert(CONS_ERROR,
"There was a problem contacting the master server...\n"
);
}
static void
Printf_url (const char *url)
{
boolean startup;
#ifdef HAVE_THREADS
I_lock_mutex(&con_mutex);
startup = con_startup;
I_unlock_mutex(con_mutex);
#else
startup = con_startup;
#endif
if (cv_masterserver_debug.value)
(startup ? I_OutputMsg : CONS_Printf)(
"HMS: connecting '%s'...\n", url);
}
static size_t
HMS_on_read (char *s, size_t _1, size_t n, void *userdata)
{
struct HMS_buffer *buffer;
size_t blocks;
(void)_1;
buffer = userdata;
while (n >= (size_t)( buffer->end - buffer->needle ))
{
/* resize to next multiple of buffer size */
blocks = ( n / DEFAULT_BUFFER_SIZE + 1 );
buffer->end += ( blocks * DEFAULT_BUFFER_SIZE );
void *tmp = realloc(buffer->buffer, buffer->end);
if (tmp == NULL)
{
// not enough memory to read it, bail
free(buffer->buffer);
buffer->buffer = NULL;
return 0;
}
buffer->buffer = tmp;
}
memcpy(&buffer->buffer[buffer->needle], s, n);
buffer->needle += n;
return n;
}
static struct HMS_buffer *HMS_connect(const char *format, ...)
{
va_list ap;
CURL *curl;
char *url;
char *quack_token;
size_t seek;
size_t token_length;
struct HMS_buffer *buffer;
CURLcode cc;
if (! hms_started)
{
if (curl_global_init(CURL_GLOBAL_ALL) != 0)
{
Contact_error();
Blame("From curl_global_init.\n");
return NULL;
}
else
{
atexit(curl_global_cleanup);
hms_started = 1;
}
}
curl = curl_easy_init();
if (! curl)
{
Contact_error();
Blame("From curl_easy_init.\n");
return NULL;
}
if (cv_masterserver_token.string && cv_masterserver_token.string[0])
{
quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0);
token_length = ( sizeof "&token="-1 ) + strlen(quack_token);
}
else
{
quack_token = NULL;
token_length = 0;
}
#ifdef HAVE_THREADS
I_lock_mutex(&hms_api_mutex);
#endif
seek = strlen(hms_api) + 1; /* + '/' */
va_start (ap, format);
size_t url_size = (seek + vsnprintf(NULL, 0, format, ap) +
sizeof HMS_QUERY_VERSION - 1 +
token_length + 1);
url = malloc(url_size);
va_end (ap);
snprintf(url, seek + 1, "%s/", hms_api);
#ifdef HAVE_THREADS
I_unlock_mutex(hms_api_mutex);
#endif
va_start (ap, format);
seek += vsnprintf(&url[seek], url_size - seek, format, ap);
va_end (ap);
strcpy(&url[seek], HMS_QUERY_VERSION);
seek += sizeof HMS_QUERY_VERSION - 1;
if (quack_token)
snprintf(&url[seek], url_size - seek, "&token=%s", quack_token);
Printf_url(url);
buffer = malloc(sizeof *buffer);
buffer->curl = curl;
buffer->end = DEFAULT_BUFFER_SIZE;
buffer->buffer = malloc(buffer->end);
buffer->needle = 0;
buffer->errbuf = malloc(CURL_ERROR_SIZE);
buffer->errbuf[0] = 0x00;
cc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, buffer->errbuf);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
if (cv_masterserver_debug.value == 2)
{
cc = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
#ifdef LOGMESSAGES
cc = curl_easy_setopt(curl, CURLOPT_STDERR, logstream);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
#endif
}
if (M_CheckParm("-bindaddr") && M_IsNextParm())
{
cc = curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm());
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
}
cc = curl_easy_setopt(curl, CURLOPT_URL, url);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
cc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
#ifdef HAVE_IPV6
if (!M_CheckParm("-ipv6"))
#endif
{
cc = curl_easy_setopt(curl, CURLOPT_IPRESOLVE, (long)CURL_IPRESOLVE_V4);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
}
cc = curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)cv_masterserver_timeout.value);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
cc = curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 30L);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
cc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
cc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
M_SetCURLArgs(curl, buffer->errbuf);
curl_free(quack_token);
free(url);
return buffer;
}
static int
HMS_do (struct HMS_buffer *buffer)
{
CURLcode cc;
long status;
char *p;
cc = curl_easy_perform(buffer->curl);
if (cc != CURLE_OK)
{
Contact_error();
Blame("From curl_easy_perform: %s\n", buffer->errbuf);
return 0;
}
buffer->buffer[buffer->needle] = '\0';
cc = curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status);
if (cc != CURLE_OK) I_OutputMsg("libcurl: %s\n", buffer->errbuf);
if (status != 200)
{
p = strchr(buffer->buffer, '\n');
if (p)
*p = '\0';
Contact_error();
Blame(
"Master server error %ld: %s%s\n",
status,
buffer->buffer,
( (p) ? "" : " (malformed)" )
);
return 0;
}
else
return 1;
}
static void
HMS_end (struct HMS_buffer *buffer)
{
curl_easy_cleanup(buffer->curl);
free(buffer->errbuf);
free(buffer->buffer);
free(buffer);
}
int
HMS_register (void)
{
struct HMS_buffer *hms;
int okay;
char post[256];
char *contact;
hms = HMS_connect(
"games/%s/%d/servers/register", SRB2APPLICATION, MODVERSION);
if (! hms)
return 0;
contact = curl_easy_escape(hms->curl, cv_server_contact.string, 0);
snprintf(post, sizeof post,
"port=%d&"
"contact=%s",
current_port,
contact
);
curl_free(contact);
curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
okay = HMS_do(hms);
if (okay)
{
hms_server_token = strdup(strtok(hms->buffer, "\n"));
}
HMS_end(hms);
return okay;
}
int
HMS_unlist (void)
{
struct HMS_buffer *hms;
int okay;
hms = HMS_connect("servers/%s/unlist", hms_server_token);
if (! hms)
return 0;
curl_easy_setopt(hms->curl, CURLOPT_POST, (long)1);
curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDSIZE, (long)0);
okay = HMS_do(hms);
HMS_end(hms);
free(hms_server_token);
return okay;
}
int
HMS_update (void)
{
struct HMS_buffer *hms;
int okay;
char post[256];
char *title;
hms = HMS_connect("servers/%s/update", hms_server_token);
if (! hms)
return 0;
title = curl_easy_escape(hms->curl, cv_servername.string, 0);
snprintf(post, sizeof post,
"title=%s",
title
);
curl_free(title);
curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
okay = HMS_do(hms);
HMS_end(hms);
return okay;
}
void
HMS_list_servers (void)
{
struct HMS_buffer *hms;
hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION);
if (! hms)
return;
if (HMS_do(hms) && cv_masterserver_debug.value)
{
CONS_Printf("%s\n", hms->buffer);
}
HMS_end(hms);
}
msg_server_t *
HMS_fetch_servers (msg_server_t *list, int query_id)
{
struct HMS_buffer *hms;
int doing_shit;
char *address;
char *port;
char *contact;
char *end;
char *p;
int i;
(void)query_id;
hms = HMS_connect("games/%s/%d/servers", SRB2APPLICATION, MODVERSION);
if (! hms)
return NULL;
if (HMS_do(hms))
{
doing_shit = 1;
p = hms->buffer;
i = 0;
while (i < MAXSERVERLIST && ( end = strchr(p, '\n') ))
{
*end = '\0';
address = strtok(p, " ");
port = strtok(NULL, " ");
contact = strtok(NULL, "");
if (address && port)
{
#ifdef HAVE_THREADS
I_lock_mutex(&ms_QueryId_mutex);
{
if (query_id != ms_QueryId)
doing_shit = 0;
}
I_unlock_mutex(ms_QueryId_mutex);
if (! doing_shit)
break;
#endif
strlcpy(list[i].ip, address, sizeof list[i].ip);
strlcpy(list[i].port, port, sizeof list[i].port);
if (contact)
{
strlcpy(list[i].contact, contact, sizeof list[i].contact);
}
list[i].header.buffer[0] = 1;
i++;
p = ( end + 1 );/* skip server delimiter */
}
else
{
/* malformed so quit the parsing */
break;
}
}
if (doing_shit)
list[i].header.buffer[0] = 0;
}
else
list = NULL;
HMS_end(hms);
return list;
}
int
HMS_compare_mod_version (char *buffer, size_t buffer_size)
{
struct HMS_buffer *hms;
int okay;
char *version;
char *version_name;
hms = HMS_connect("games/%s/version", SRB2APPLICATION);
if (! hms)
return 0;
okay = 0;
if (HMS_do(hms))
{
version = strtok(hms->buffer, " ");
version_name = strtok(NULL, "\n");
if (version && version_name)
{
if (atoi(version) != MODVERSION)
{
strlcpy(buffer, version_name, buffer_size);
okay = 1;
}
else
okay = -1;
}
}
HMS_end(hms);
return okay;
}
const char *
HMS_fetch_rules (char *buffer, size_t buffer_size)
{
struct HMS_buffer *hms;
hms = HMS_connect("rules");
if (! hms)
return NULL;
boolean okay = HMS_do(hms);
if (okay)
{
char *p = strstr(hms->buffer, "\n\n");
if (p)
{
p[1] = '\0';
strlcpy(buffer, hms->buffer, buffer_size);
}
else
buffer = NULL;
}
else
buffer = NULL;
HMS_end(hms);
if (!okay)
return NULL;
return buffer;
}
static char *
Strip_trailing_slashes (char *api)
{
char * p = &api[strlen(api)];
while (*--p == '/')
;
p[1] = '\0';
return api;
}
void
HMS_set_api (char *api)
{
#ifdef HAVE_THREADS
I_lock_mutex(&hms_api_mutex);
#endif
{
free(hms_api);
hms_api = Strip_trailing_slashes(api);
}
#ifdef HAVE_THREADS
I_unlock_mutex(hms_api_mutex);
#endif
}
#endif/*MASTERSERVER*/
static void
MasterServer_Debug_OnChange (void)
{
#ifdef MASTERSERVER
/* TODO: change to 'latest-log.txt' for log files revision. */
if (cv_masterserver_debug.value == 2)
CONS_Printf("Master server debug messages will appear in log.txt\n");
#endif
}