Skip to content
Snippets Groups Projects
Select Git revision
  • vectors-matrices-quaternions
  • next default protected
  • master protected
  • better-distance-math
  • movie
  • softcode-info
  • acs
  • clipmidtex
  • custom-map-names
  • nogravity-trampolines
  • 2214-pre4
  • 2214-pre3
  • just-in-case
  • fix-opengl-parameter-crash
  • 2214-pre2
  • 2214-pre1
  • delfile2
  • cleanupmusic
  • gametype-refactor-1
  • extra-textures
  • SRB2_release_2.2.15
  • SRB2_release_2.2.13
  • SRB2_release_2.2.12
  • SRB2_release_2.2.11
  • SRB2_release_2.2.10
  • SRB2_release_2.2.9
  • SRB2_release_2.2.8
  • SRB2_release_2.2.7
  • SRB2_release_2.2.6
  • SRB2_release_2.2.5
  • SRB2_release_2.2.4
  • SRB2_release_2.2.3
  • SRB2_release_2.2.2
  • SRB2_release_2.2.1
  • SRB2_release_2.2.0
  • SRB2_release_2.1.25
  • SRB2_release_2.1.24
  • SRB2_release_2.1.23
  • SRB2_release_2.1.22
  • SRB2_release_2.1.21
40 results

http-mserv.c

Blame
  • http-mserv.c 11.75 KiB
    // SONIC ROBO BLAST 2
    //-----------------------------------------------------------------------------
    // Copyright (C) 2020-2023 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.
    
                         <http://mb.srb2.org/MS/tools/api/v1/>
    */
    
    #ifdef HAVE_CURL
    #include <curl/curl.h>
    #endif
    
    #include "doomdef.h"
    #include "d_clisrv.h"
    #include "command.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 = CVAR_INIT
    (
    		"masterserver_timeout", "5", CV_SAVE, CV_Unsigned,
    		NULL
    );
    
    consvar_t cv_masterserver_debug = CVAR_INIT
    (
    	"masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff,
    	MasterServer_Debug_OnChange
    );
    
    consvar_t cv_masterserver_token = CVAR_INIT
    (
    		"masterserver_token", "", CV_SAVE, NULL,
    		NULL
    );
    
    #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;
    
    static char hms_useragent[512];
    
    struct HMS_buffer
    {
    	CURL *curl;
    	char *buffer;
    	int   needle;
    	int    end;
    };
    
    static void
    Contact_error (void)
    {
    	CONS_Alert(CONS_ERROR,
    			"There was a problem contacting the master server...\n"
    	);
    }
    
    static void
    get_user_agent(char *buf, size_t len)
    {
    	if (snprintf(buf, len, "%s/%s (%s; %s; %i; %i) SRB2BASE/%i", SRB2APPLICATION, VERSIONSTRING, compbranch, comprevision,  MODID, MODVERSION, CODEBASE) < 0)
    		I_Error("http-mserv: get_user_agent failed");
    }
    
    static void
    init_user_agent_once(void)
    {
    	if (hms_useragent[0] != '\0')
    		return;
    	
    	get_user_agent(hms_useragent, 512);
    }
    
    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;
    
    	if (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 );
    
    		buffer->buffer = realloc(buffer->buffer, buffer->end);
    	}
    
    	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;
    
    	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
    
    	init_user_agent_once();
    
    	seek = strlen(hms_api) + 1;/* + '/' */
    
    	va_start (ap, format);
    	url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1);
    	va_end (ap);
    
    	sprintf(url, "%s/", hms_api);
    
    #ifdef HAVE_THREADS
    	I_unlock_mutex(hms_api_mutex);
    #endif
    
    	va_start (ap, format);
    	seek += vsprintf(&url[seek], format, ap);
    	va_end (ap);
    
    	if (quack_token)
    		sprintf(&url[seek], "?token=%s", quack_token);
    
    	CONS_Printf("HMS: connecting '%s'...\n", url);
    
    	buffer = malloc(sizeof *buffer);
    	buffer->curl = curl;
    	buffer->end = DEFAULT_BUFFER_SIZE;
    	buffer->buffer = malloc(buffer->end);
    	buffer->needle = 0;
    
    	if (cv_masterserver_debug.value)
    	{
    		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    		curl_easy_setopt(curl, CURLOPT_STDERR, logstream);
    	}
    
    	if (M_CheckParm("-bindaddr") && M_IsNextParm())
    	{
    		curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm());
    	}
    
    	curl_easy_setopt(curl, CURLOPT_URL, url);
    	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    
    #ifndef NO_IPV6
    	if (M_CheckParm("-noipv6"))
    #endif
    		curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
    
    	curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value);
    	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read);
    	curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
    
    	curl_easy_setopt(curl, CURLOPT_USERAGENT, hms_useragent);
    
    	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",
    				curl_easy_strerror(cc)
    		);
    		return 0;
    	}
    
    	buffer->buffer[buffer->needle] = '\0';
    
    	curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status);
    
    	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->buffer);
    	free(buffer);
    }
    
    int
    HMS_fetch_rooms (int joining, int query_id)
    {
    	struct HMS_buffer *hms;
    	int ok;
    
    	int doing_shit;
    
    	char *id;
    	char *title;
    	char *room_motd;
    
    	int id_no;
    
    	char *p;
    	char *end;
    
    	int i;
    
    	(void)query_id;
    
    	hms = HMS_connect("rooms");
    
    	if (! hms)
    		return 0;
    
    	if (HMS_do(hms))
    	{
    		doing_shit = 1;
    
    		p = hms->buffer;
    
    		for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );)
    		{
    			*end = '\0';
    
    			id    = strtok(p, "\n");
    			title = strtok(0, "\n");
    			room_motd = strtok(0, "");
    
    			if (id && title && room_motd)
    			{
    				id_no = atoi(id);
    
    				/*
    				Don't show the 'All' room if hosting. And it's a hack like this
    				because I'm way too lazy to add another feature to the MS.
    				*/
    				if (joining || id_no != 0)
    				{
    #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
    
    					room_list[i].header.buffer[0] = 1;
    
    					room_list[i].id = id_no;
    					strlcpy(room_list[i].name, title, sizeof room_list[i].name);
    					strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd);
    
    					i++;
    				}
    
    				p = ( end + 3 );/* skip the three linefeeds */
    			}
    			else
    				break;
    		}
    
    		if (doing_shit)
    			room_list[i].header.buffer[0] = 0;
    
    		ok = 1;
    
    		if (doing_shit)
    		{
    #ifdef HAVE_THREADS
    			I_lock_mutex(&m_menu_mutex);
    #endif
    			{
    				for (i = 0; room_list[i].header.buffer[0]; i++)
    				{
    					if(*room_list[i].name != '\0')
    					{
    						MP_RoomMenu[i+1].text = room_list[i].name;
    						roomIds[i] = room_list[i].id;
    						MP_RoomMenu[i+1].status = IT_STRING|IT_CALL;
    					}
    				}
    			}
    #ifdef HAVE_THREADS
    			I_unlock_mutex(m_menu_mutex);
    #endif
    		}
    	}
    	else
    		ok = 0;
    
    	HMS_end(hms);
    
    	return ok;
    }
    
    int
    HMS_register (void)
    {
    	struct HMS_buffer *hms;
    	int ok;
    
    	char post[256];
    
    	char *title;
    
    	hms = HMS_connect("rooms/%d/register", ms_RoomId);
    
    	if (! hms)
    		return 0;
    
    	title = curl_easy_escape(hms->curl, cv_servername.string, 0);
    
    	snprintf(post, sizeof post,
    			"port=%d&"
    			"title=%s&"
    			"version=%s",
    
    			current_port,
    
    			title,
    
    			SRB2VERSION
    	);
    
    	curl_free(title);
    
    	curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post);
    
    	ok = HMS_do(hms);
    
    	if (ok)
    	{
    		hms_server_token = strdup(strtok(hms->buffer, "\n"));
    	}
    
    	HMS_end(hms);
    
    	return ok;
    }
    
    int
    HMS_unlist (void)
    {
    	struct HMS_buffer *hms;
    	int ok;
    
    	hms = HMS_connect("servers/%s/unlist", hms_server_token);
    
    	if (! hms)
    		return 0;
    
    	curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST");
    
    	ok = HMS_do(hms);
    	HMS_end(hms);
    
    	free(hms_server_token);
    
    	return ok;
    }
    
    int
    HMS_update (void)
    {
    	struct HMS_buffer *hms;
    	int ok;
    
    	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);
    
    	ok = HMS_do(hms);
    	HMS_end(hms);
    
    	return ok;
    }
    
    void
    HMS_list_servers (void)
    {
    	struct HMS_buffer *hms;
    
    	char *list;
    	char *p;
    
    	hms = HMS_connect("servers");
    
    	if (! hms)
    		return;
    
    	if (HMS_do(hms))
    	{
    		list = curl_easy_unescape(hms->curl, hms->buffer, 0, NULL);
    
    		p = strtok(list, "\n");
    
    		while (p != NULL)
    		{
    			CONS_Printf("\x80%s\n", p);
    			p = strtok(NULL, "\n");
    		}
    
    		curl_free(list);
    	}
    
    	HMS_end(hms);
    }
    
    msg_server_t *
    HMS_fetch_servers (msg_server_t *list, int room_number, int query_id)
    {
    	struct HMS_buffer *hms;
    
    	int doing_shit;
    
    	char local_version[9];
    
    	char *room;
    
    	char *address;
    	char *port;
    	char *title;
    	char *version;
    
    	char *end;
    	char *section_end;
    	char *p;
    
    	int i;
    
    	(void)query_id;
    
    	if (room_number > 0)
    	{
    		hms = HMS_connect("rooms/%d/servers", room_number);
    	}
    	else
    		hms = HMS_connect("servers");
    
    	if (! hms)
    		return NULL;
    
    	if (HMS_do(hms))
    	{
    		doing_shit = 1;
    
    		snprintf(local_version, sizeof local_version,
    				"%s",
    				SRB2VERSION
    		);
    
    		p = hms->buffer;
    		i = 0;
    
    		do
    		{
    			section_end = strstr(p, "\n\n");
    
    			room = strtok(p, "\n");
    
    			p = strtok(0, "");
    
    			if (! p)
    				break;
    
    			while (i < MAXSERVERLIST && ( end = strchr(p, '\n') ))
    			{
    				*end = '\0';
    
    				address = strtok(p, " ");
    				port    = strtok(0, " ");
    				title   = strtok(0, " ");
    				version = strtok(0, "");
    
    				if (address && port && title && version)
    				{
    #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
    
    					if (strcmp(version, local_version) == 0)
    					{
    						strlcpy(list[i].ip,      address, sizeof list[i].ip);
    						strlcpy(list[i].port,    port,    sizeof list[i].port);
    						strlcpy(list[i].name,    title,   sizeof list[i].name);
    						strlcpy(list[i].version, version, sizeof list[i].version);
    
    						list[i].room = atoi(room);
    
    						list[i].header.buffer[0] = 1;
    
    						i++;
    					}
    
    					if (end == section_end)/* end of list for this room */
    						break;
    					else
    						p = ( end + 1 );/* skip server delimiter */
    				}
    				else
    				{
    					section_end = 0;/* malformed so quit the parsing */
    					break;
    				}
    			}
    
    			if (! doing_shit)
    				break;
    
    			p = ( section_end + 2 );
    		}
    		while (section_end) ;
    
    		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 ok;
    
    	char *version;
    	char *version_name;
    
    	hms = HMS_connect("versions/%d", MODID);
    
    	if (! hms)
    		return 0;
    
    	ok = 0;
    
    	if (HMS_do(hms))
    	{
    		version      = strtok(hms->buffer, " ");
    		version_name = strtok(0, "\n");
    
    		if (version && version_name)
    		{
    			if (atoi(version) != MODVERSION)
    			{
    				strlcpy(buffer, version_name, buffer_size);
    				ok = 1;
    			}
    			else
    				ok = -1;
    		}
    	}
    
    	HMS_end(hms);
    
    	return ok;
    }
    
    void
    HMS_set_api (char *api)
    {
    #ifdef HAVE_THREADS
    	I_lock_mutex(&hms_api_mutex);
    #endif
    	{
    		free(hms_api);
    		hms_api = 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)
    		CONS_Printf("Master server debug messages will appear in log.txt\n");
    #endif
    }