diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c43464b7..9854567e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -364,8 +364,9 @@ if(${SRB2_CONFIG_HAVE_DISCORDRPC}) if(${DISCORDRPC_FOUND}) set(SRB2_HAVE_DISCORDRPC ON) add_definitions(-DHAVE_DISCORDRPC) - set(SRB2_DISCORDRPC_SOURCES discord.c) - set(SRB2_DISCORDRPC_HEADERS discord.h) + add_definitions(-DUSE_STUN) + set(SRB2_DISCORDRPC_SOURCES discord.c stun.c) + set(SRB2_DISCORDRPC_HEADERS discord.h stun.h) prepend_sources(SRB2_DISCORDRPC_SOURCES) prepend_sources(SRB2_DISCORDRPC_HEADERS) source_group("Discord Rich Presence" FILES ${SRB2_DISCORDRPC_SOURCES} ${SRB2_DISCORDRPC_HEADERS}) diff --git a/src/Makefile b/src/Makefile index fb859a33..da96f0cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -440,8 +440,8 @@ endif ifdef HAVE_DISCORDRPC LIBS+=-ldiscord-rpc -CFLAGS+=-DHAVE_DISCORDRPC -OBJS+=$(OBJDIR)/discord.o +CFLAGS+=-DHAVE_DISCORDRPC -DUSE_STUN +OBJS+=$(OBJDIR)/discord.o $(OBJDIR)/stun.o endif ifndef NO_LUA diff --git a/src/d_clisrv.h b/src/d_clisrv.h index b88e7936..b7fc56ec 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -502,6 +502,7 @@ extern INT32 mapchangepending; // Points inside doomcom extern doomdata_t *netbuffer; +extern consvar_t cv_stunserver; extern consvar_t cv_httpsource; extern consvar_t cv_showjoinaddress; extern consvar_t cv_playbackspeed; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 4a90de33..84c1045a 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -714,6 +714,10 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_dummyconsvar); +#ifdef USE_STUN + CV_RegisterVar(&cv_stunserver); +#endif + CV_RegisterVar(&cv_discordinvites); RegisterNetXCmd(XD_DISCORD, Got_DiscordInfo); } diff --git a/src/discord.c b/src/discord.c index b3798e29..4f72b714 100644 --- a/src/discord.c +++ b/src/discord.c @@ -12,9 +12,7 @@ #ifdef HAVE_DISCORDRPC -#ifdef HAVE_CURL -#include -#endif // HAVE_CURL +#include #include "i_system.h" #include "d_clisrv.h" @@ -27,6 +25,8 @@ #include "mserv.h" // cv_advertise #include "z_zone.h" #include "byteptr.h" +#include "stun.h" +#include "i_tcp.h" // current_port #include "discord.h" #include "doomdef.h" @@ -45,16 +45,7 @@ struct discordInfo_s discordInfo; discordRequest_t *discordRequestList = NULL; -#ifdef HAVE_CURL -struct SelfIPbuffer -{ - CURL *curl; - char *pointer; - size_t length; -}; - static char self_ip[IP_SIZE]; -#endif // HAVE_CURL /*-------------------------------------------------- static char *DRPC_XORIPString(const char *input) @@ -335,39 +326,23 @@ void DRPC_Init(void) DRPC_UpdatePresence(); } -#ifdef HAVE_CURL /*-------------------------------------------------- - static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata) + static void DRPC_GotServerIP(UINT32 address) - Writing function for use with curl. Only intended to be used with simple text. + Callback triggered by successful STUN response. Input Arguments:- - s - Data to write - size - Always 1. - n - Length of data - userdata - Passed in from CURLOPT_WRITEDATA, intended to be SelfIPbuffer + address - IPv4 address of this machine, in network byte order. Return:- - Number of bytes wrote in this pass. + None --------------------------------------------------*/ -static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata) +static void DRPC_GotServerIP(UINT32 address) { - struct SelfIPbuffer *buffer; - size_t newlength; - - buffer = userdata; - - newlength = buffer->length + size*n; - buffer->pointer = realloc(buffer->pointer, newlength+1); - - memcpy(buffer->pointer + buffer->length, s, size*n); - - buffer->pointer[newlength] = '\0'; - buffer->length = newlength; - - return size*n; + const unsigned char * p = (const unsigned char *)&address; + sprintf(self_ip, "%u.%u.%u.%u:%u", p[0], p[1], p[2], p[3], current_port); + DRPC_UpdatePresence(); } -#endif // HAVE_CURL /*-------------------------------------------------- static const char *DRPC_GetServerIP(void) @@ -387,64 +362,21 @@ static const char *DRPC_GetServerIP(void) { // We're not the server, so we could successfully get the IP! // No need to do anything else :) - return address; + sprintf(self_ip, "%s:%u", address, current_port); + return self_ip; } } -#ifdef HAVE_CURL - // This is a little bit goofy, but - // there's practically no good way to get your own public IP address, - // so we've gotta break out curl for this :V - if (!self_ip[0]) - { - CURL *curl; - - curl_global_init(CURL_GLOBAL_ALL); - curl = curl_easy_init(); - - if (curl) - { - // The API to get your public IP address from. - // Picked because it's stupid simple and it's been up for a long time. - const char *api = "http://ip4only.me/api/"; - - struct SelfIPbuffer buffer; - CURLcode success; - - buffer.length = 0; - buffer.pointer = malloc(buffer.length+1); - buffer.pointer[0] = '\0'; - - curl_easy_setopt(curl, CURLOPT_URL, api); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, DRPC_WriteServerIP); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); - - success = curl_easy_perform(curl); - - if (success == CURLE_OK) - { - char *tmp; - tmp = strtok(buffer.pointer, ","); - - if (!strcmp(tmp, "IPv4")) // ensure correct type of IP - { - tmp = strtok(NULL, ","); - strncpy(self_ip, tmp, IP_SIZE); // Yay, we have the IP :) - } - } - - free(buffer.pointer); - curl_easy_cleanup(curl); - } - - curl_global_cleanup(); - } - if (self_ip[0]) + { return self_ip; + } else -#endif // HAVE_CURL - return NULL; // Could not get your IP for whatever reason, so we cannot do Discord invites + { + // There happens to be a good way to get it after all! :D + STUN_bind(DRPC_GotServerIP); + return NULL; + } } /*-------------------------------------------------- @@ -510,19 +442,6 @@ void DRPC_UpdatePresence(void) // Server info if (netgame) { - if (cv_advertise.value) - { - discordPresence.state = "Public"; - } - else - { - discordPresence.state = "Private"; - } - - discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field! - discordPresence.partySize = D_NumPlayers(); // Players in server - discordPresence.partyMax = discordInfo.maxPlayers; // Max players - if (DRPC_InvitesAreAllowed() == true) { const char *join; @@ -536,7 +455,24 @@ void DRPC_UpdatePresence(void) joinSecretSet = true; } + else + { + return; + } } + + if (cv_advertise.value) + { + discordPresence.state = "Public"; + } + else + { + discordPresence.state = "Private"; + } + + discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field! + discordPresence.partySize = D_NumPlayers(); // Players in server + discordPresence.partyMax = discordInfo.maxPlayers; // Max players } else { diff --git a/src/i_tcp.c b/src/i_tcp.c index 1f1cf4f2..ba973494 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -182,6 +182,7 @@ static UINT8 UPNP_support = TRUE; #include "d_netfil.h" #include "i_tcp.h" #include "m_argv.h" +#include "stun.h" #include "doomstat.h" @@ -612,6 +613,13 @@ static boolean SOCK_Get(void) (void *)&fromaddress, &fromlen); if (c != ERRSOCKET) { +#ifdef USE_STUN + if (STUN_got_response(doomcom->data, c)) + { + return false; + } +#endif + // find remote node number for (j = 1; j <= MAXNETNODES; j++) //include LAN { diff --git a/src/m_swap.h b/src/m_swap.h index 2d42f613..c1e5e39b 100644 --- a/src/m_swap.h +++ b/src/m_swap.h @@ -16,16 +16,12 @@ #include "endian.h" -// Endianess handling. -// WAD files are stored little endian. -#ifdef SRB2_BIG_ENDIAN - -#define SHORT(x) ((INT16)(\ +#define SWAP_SHORT(x) ((INT16)(\ (((UINT16)(x) & (UINT16)0x00ffU) << 8) \ | \ (((UINT16)(x) & (UINT16)0xff00U) >> 8))) \ -#define LONG(x) ((INT32)(\ +#define SWAP_LONG(x) ((INT32)(\ (((UINT32)(x) & (UINT32)0x000000ffUL) << 24) \ | \ (((UINT32)(x) & (UINT32)0x0000ff00UL) << 8) \ @@ -34,9 +30,18 @@ | \ (((UINT32)(x) & (UINT32)0xff000000UL) >> 24))) +// Endianess handling. +// WAD files are stored little endian. +#ifdef SRB2_BIG_ENDIAN +#define SHORT SWAP_SHORT +#define LONG SWAP_LONG +#define MSBF_SHORT(x) ((INT16)(x)) +#define MSBF_LONG(x) ((INT32)(x)) #else #define SHORT(x) ((INT16)(x)) #define LONG(x) ((INT32)(x)) +#define MSBF_SHORT SWAP_SHORT +#define MSBF_LONG SWAP_LONG #endif #endif diff --git a/src/stun.c b/src/stun.c new file mode 100644 index 00000000..dbcfe58e --- /dev/null +++ b/src/stun.c @@ -0,0 +1,236 @@ +// 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. +//----------------------------------------------------------------------------- +/// \file stun.c +/// \brief RFC 5389 client implementation to fetch external IP address. + +/* https://tools.ietf.org/html/rfc5389 */ + +#if defined (__linux__) +#include +#elif defined (_WIN32) +#define _CRT_RAND_S +#elif defined (__APPLE__) +#include +#else +#error "Need CSPRNG." +#endif + +#include "doomdef.h" +#include "d_clisrv.h" +#include "command.h" +#include "i_net.h" +#include "stun.h" + +/* https://gist.github.com/zziuni/3741933 */ +/* I can only trust google to keep their shit up :y */ +consvar_t cv_stunserver = { + "stunserver", "stun.l.google.com:19302", CV_SAVE, NULL, + NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +static stun_callback_t stun_callback; + +/* 18.4 STUN UDP and TCP Port Numbers */ + +#define STUN_PORT "3478" + +/* 6. STUN Message Structure */ + +#define BIND_REQUEST 0x0001 +#define BIND_RESPONSE 0x0101 + +static const UINT32 MAGIC_COOKIE = MSBF_LONG (0x2112A442); + +static char transaction_id[12]; + +/* 18.2 STUN Attribute Registry */ + +#define XOR_MAPPED_ADDRESS 0x0020 + +/* 15.1 MAPPED-ADDRESS */ + +#define STUN_IPV4 0x01 + +static SINT8 +STUN_node (void) +{ + SINT8 node; + + char * const colon = strchr(cv_stunserver.zstring, ':'); + + const char * const host = cv_stunserver.zstring; + const char * const port = &colon[1]; + + I_Assert(I_NetMakeNodewPort != NULL); + + if (colon != NULL) + { + *colon = '\0'; + + node = I_NetMakeNodewPort(host, port); + + *colon = ':'; + } + else + { + node = I_NetMakeNodewPort(host, STUN_PORT); + } + + return node; +} + +static void +csprng +( + void * const buffer, + const size_t size +){ +#if defined (_WIN32) + size_t o; + + for (o = 0; o < size; o += sizeof (unsigned int)) + { + rand_s((unsigned int *)&((char *)buffer)[o]); + } +#elif defined (__linux__) + getrandom(buffer, size, 0U); +#elif defined (__APPLE__) + CCRandomGenerateBytes(buffer, size); +#elif defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) + arc4random_buf(buffer, size); +#endif +} + +void +STUN_bind (stun_callback_t callback) +{ + /* 6. STUN Message Structure */ + + const UINT16 type = MSBF_SHORT (BIND_REQUEST); + + const SINT8 node = STUN_node(); + + doomcom->remotenode = node; + doomcom->datalength = 20; + + csprng(transaction_id, 12U); + + memcpy(&doomcom->data[0], &type, 2U); + memset(&doomcom->data[2], 0, 2U); + memcpy(&doomcom->data[4], &MAGIC_COOKIE, 4U); + memcpy(&doomcom->data[8], transaction_id, 12U); + + stun_callback = callback; + + I_NetSend(); + Net_CloseConnection(node);/* will handle response at I_NetGet */ +} + +static size_t +STUN_xor_mapped_address (const char * const value) +{ + const UINT32 xaddr = *(const UINT32 *)&value[4]; + const UINT32 addr = xaddr ^ MAGIC_COOKIE; + + (*stun_callback)(addr); + + return 0U; +} + +static size_t +align4 (size_t n) +{ + return n + n % 4U; +} + +static size_t +STUN_parse_attribute (const char * const attribute) +{ + /* 15. STUN Attributes */ + const UINT16 type = MSBF_SHORT (*(const UINT16 *)&attribute[0]); + const UINT16 length = MSBF_SHORT (*(const UINT16 *)&attribute[2]); + + /* 15.2 XOR-MAPPED-ADDRESS */ + if ( + type == XOR_MAPPED_ADDRESS && + length == 8U && + (unsigned char)attribute[5] == STUN_IPV4 + ){ + return STUN_xor_mapped_address(&attribute[4]); + } + + return align4(4U + length); +} + +boolean +STUN_got_response +( + const char * const buffer, + const size_t size +){ + const char * const end = &buffer[size]; + + const char * p = &buffer[20]; + + UINT16 type; + UINT16 length; + + /* + Check for STUN response. + + Header is 20 bytes. + XOR-MAPPED-ADDRESS attribute is required. + Each attribute has a 2 byte header. + The XOR-MAPPED-ADDRESS attribute also has a 8 byte value. + This totals 10 bytes for the attribute. + */ + + if (size < 30U || stun_callback == NULL) + { + return false; + } + + /* 6. STUN Message Structure */ + + if ( + *(const UINT32 *)&buffer[4] == MAGIC_COOKIE && + memcmp(&buffer[8], transaction_id, 12U) == 0 + ){ + type = MSBF_SHORT (*(const UINT16 *)&buffer[0]); + length = MSBF_SHORT (*(const UINT16 *)&buffer[2]); + + if ( + (type >> 14) == 0U && + (length & 0x02) == 0U && + (20U + length) <= size + ){ + if (type == BIND_RESPONSE) + { + do + { + length = STUN_parse_attribute(p); + + if (length == 0U) + { + break; + } + + p += length; + } + while (p < end) ; + } + + stun_callback = NULL; + + return true; + } + } + + return false; +} diff --git a/src/stun.h b/src/stun.h new file mode 100644 index 00000000..de23aeb4 --- /dev/null +++ b/src/stun.h @@ -0,0 +1,20 @@ +// 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. +//----------------------------------------------------------------------------- +/// \file stun.h +/// \brief RFC 5389 client implementation to fetch external IP address. + +#ifndef KART_STUN_H +#define KART_STUN_H + +typedef void (*stun_callback_t)(UINT32 address); + +void STUN_bind (stun_callback_t); +boolean STUN_got_response (const char * const buffer, const size_t size); + +#endif/*KART_STUN_H*/