1
0
Fork 0
forked from fte/fteqw
fteqw/engine/client/valid.c
Spoike c68cbfec24 implement status command for nq clients that expect something to be printed. IP addresses are withheld.
fix stats issue.
support menuqc-based loading screens.
fixed 2d render-to-texture issues.
(finally) throttle 'connection lost or aborted' messages. report the ip address too, because we can.
begun work on nq-style player ip-logging. ip addresses are collected now, but there's still no actual database code yet.
rewrote engine auto-update. now part of the updates menu instead of system-specific special-case code. Still requires a system function to actually invoke the updated engine however.
added sdl audio capture support (requires sdl 2.0.5).
treat q_version like f_version etc. respond to both.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5046 fc73d0e0-1445-4013-8a0c-d673dee63da5
2017-01-24 10:27:39 +00:00

677 lines
18 KiB
C

#include "quakedef.h"
#include <ctype.h>
#ifdef _WIN32
#include "winquake.h"
#endif
typedef struct f_modified_s {
char name[MAX_QPATH];
qboolean ismodified;
struct f_modified_s *next;
} f_modified_t;
static f_modified_t *f_modified_list;
qboolean care_f_modified;
qboolean f_modified_particles;
static void QDECL rulesetcallback(cvar_t *var, char *oldval)
{
Validation_Apply_Ruleset();
}
cvar_t allow_f_version = CVAR("allow_f_version", "1");
cvar_t allow_f_server = CVAR("allow_f_server", "1");
cvar_t allow_f_modified = CVAR("allow_f_modified", "1");
cvar_t allow_f_skins = CVAR("allow_f_skins", "1");
cvar_t allow_f_ruleset = CVAR("allow_f_ruleset", "1");
cvar_t allow_f_scripts = CVAR("allow_f_scripts", "1");
cvar_t allow_f_fakeshaft = CVAR("allow_f_fakeshaft", "1");
cvar_t allow_f_system = CVAR("allow_f_system", "0");
cvar_t allow_f_cmdline = CVAR("allow_f_cmdline", "0");
cvar_t auth_validateclients = CVAR("auth_validateclients", "1");
cvar_t ruleset = CVARC("ruleset", "none", rulesetcallback);
#define SECURITY_INIT_BAD_CHECKSUM 1
#define SECURITY_INIT_BAD_VERSION 2
#define SECURITY_INIT_ERROR 3
#define SECURITY_INIT_NOPROC 4
typedef struct signed_buffer_s {
qbyte *buf;
unsigned long size;
} signed_buffer_t;
typedef signed_buffer_t *(*Security_Verify_Response_t) (int playernum, unsigned char *, char *userinfo, char *serverinfo);
typedef int (*Security_Init_t) (char *);
typedef signed_buffer_t *(*Security_Generate_Crc_t) (int playernum, char *userinfo, char *serverinfo);
typedef signed_buffer_t *(*Security_IsModelModified_t) (char *, int, qbyte *, int);
typedef void (*Security_Supported_Binaries_t) (void *);
typedef void (*Security_Shutdown_t) (void);
static Security_Verify_Response_t Security_Verify_Response;
static Security_Init_t Security_Init;
static Security_Generate_Crc_t Security_Generate_Crc;
static Security_IsModelModified_t Security_IsModelModified;
static Security_Supported_Binaries_t Security_Supported_Binaries;
static Security_Shutdown_t Security_Shutdown;
#if 0//def _WIN32
static void *secmodule;
#endif
static void Validation_Version(void)
{
char sr[256];
char *s = sr;
char authbuf[256];
char *auth = authbuf;
extern cvar_t r_drawflat;
//print certain allowed 'cheat' options.
//realtime lighting (shadows can show around corners)
//drawflat is just lame
//24bits can be considered eeeevil, by some.
#ifdef RTLIGHTS
if (r_shadow_realtime_world.ival)
*s++ = 'W';
else if (r_shadow_realtime_dlight.ival)
*s++ = 'S';
#endif
if (r_drawflat.ival || r_lightmap.ival)
*s++ = 'F';
if (gl_load24bit.ival)
*s++ = 'H';
*s = '\0';
if (!allow_f_version.ival)
return; //suppress it
if (Security_Generate_Crc)
{
signed_buffer_t *resp;
resp = Security_Generate_Crc(cl.playerview[0].playernum, cl.players[cl.playerview[0].playernum].userinfo, cl.serverinfo);
if (!resp || !resp->buf)
auth = "";
else
Q_snprintfz(auth, sizeof(authbuf), " crc: %s", resp->buf);
}
else
auth = "";
if (*sr)
Cbuf_AddText (va("say %s "PLATFORM"/%s/%s%s\n", version_string(), q_renderername, sr, auth), RESTRICT_RCON);
else
Cbuf_AddText (va("say %s "PLATFORM"/%s%s\n", version_string(), q_renderername, auth), RESTRICT_RCON);
}
void Validation_CheckIfResponse(char *text)
{
//client name, version type(os-renderer where it matters, os/renderer where renderer doesn't), 12 char hex crc
int f_query_client;
int i;
char *crc;
char *versionstring;
if (!Security_Verify_Response)
return; //valid or not, we can't check it.
if (!auth_validateclients.ival)
return;
//do the parsing.
{
char *comp;
int namelen;
for (crc = text + strlen(text) - 1; crc > text; crc--)
if ((unsigned)*crc > ' ')
break;
//find the crc.
for (i = 0; i < 29; i++)
{
if (crc <= text)
return; //not enough chars.
if ((unsigned)crc[-1] <= ' ')
break;
crc--;
}
//we now want 3 string seperated tokens, so the first starts at the fourth found ' ' + 1
i = 7;
for (comp = crc-1; ; comp--)
{
if (comp < text)
return;
if (*comp == ' ')
{
i--;
if (!i)
break;
}
}
versionstring = comp+1;
if (comp <= text)
return; //not enough space for the 'name:'
if (*(comp-1) != ':')
return; //whoops. not a say.
namelen = comp - text-1;
for (f_query_client = 0; f_query_client < cl.allocated_client_slots; f_query_client++)
{
if (strlen(cl.players[f_query_client].name) == namelen)
if (!strncmp(cl.players[f_query_client].name, text, namelen))
break;
}
if (f_query_client == cl.allocated_client_slots)
return; //looks like a validation, but it's not from a known client.
}
{
char *match = DISTRIBUTION" v";
if (strncmp(versionstring, match, strlen(match)))
return; //this is not us
}
//now do the validation
{
signed_buffer_t *resp;
resp = Security_Verify_Response(f_query_client, crc, cl.players[f_query_client].userinfo, cl.serverinfo);
if (resp && resp->size && *resp->buf)
Con_Printf(CON_NOTICE "Authentication Successful.\n");
else// if (!resp)
Con_Printf(CON_ERROR "AUTHENTICATION FAILED.\n");
}
}
void InitValidation(void)
{
Cvar_Register(&allow_f_version, "Authentication");
Cvar_Register(&allow_f_server, "Authentication");
Cvar_Register(&allow_f_modified, "Authentication");
Cvar_Register(&allow_f_skins, "Authentication");
Cvar_Register(&allow_f_ruleset, "Authentication");
Cvar_Register(&allow_f_fakeshaft, "Authentication");
Cvar_Register(&allow_f_scripts, "Authentication");
Cvar_Register(&allow_f_system, "Authentication");
Cvar_Register(&allow_f_cmdline, "Authentication");
Cvar_Register(&ruleset, "Authentication");
#if 0//def _WIN32
secmodule = LoadLibrary("fteqw-security.dll");
if (secmodule)
{
Security_Verify_Response = (void*)GetProcAddress(secmodule, "Security_Verify_Response");
Security_Init = (void*)GetProcAddress(secmodule, "Security_Init");
Security_Generate_Crc = (void*)GetProcAddress(secmodule, "Security_Generate_Crc");
Security_IsModelModified = (void*)GetProcAddress(secmodule, "Security_IsModelModified");
Security_Supported_Binaries = (void*)GetProcAddress(secmodule, "Security_Supported_Binaries");
Security_Shutdown = (void*)GetProcAddress(secmodule, "Security_Shutdown");
}
#endif
if (Security_Init)
{
switch(Security_Init(va("%s %.2f %i", DISTRIBUTION, 2.57, version_number())))
{
case SECURITY_INIT_BAD_CHECKSUM:
Con_Printf("Checksum failed. Security module does not support this build. Go upgrade it.\n");
break;
case SECURITY_INIT_BAD_VERSION:
Con_Printf("Version failed. Security module does not support this version. Go upgrade.\n");
break;
case SECURITY_INIT_ERROR:
Con_Printf("'Generic' security error. Stop hacking.\n");
break;
case SECURITY_INIT_NOPROC:
Con_Printf("/proc/* does not exist. You will need to upgrade/reconfigure your kernel.\n");
break;
case 0:
Cvar_Register(&auth_validateclients, "Authentication");
return;
}
#if 0//def _WIN32
FreeLibrary(secmodule);
#endif
}
Security_Verify_Response = NULL;
Security_Init = NULL;
Security_Generate_Crc = NULL;
Security_IsModelModified = NULL;
Security_Supported_Binaries = NULL;
Security_Shutdown = NULL;
}
//////////////////////
//f_modified
void Validation_IncludeFile(char *filename, char *file, int filelen)
{
}
static void Validation_FilesModified (void)
{
Con_Printf ("f_modified not implemented\n");
}
void Validation_FlushFileList(void)
{
f_modified_t *fm;
while(f_modified_list)
{
fm = f_modified_list->next;
Z_Free(f_modified_list);
f_modified_list = fm;
}
}
/////////////////////////
//minor (codewise) responses
static void Validation_Server(void)
{
char adr[MAX_ADR_SIZE];
#ifdef warningmsg
#pragma warningmsg("is allowing the user to turn this off practical?..")
#endif
if (!allow_f_server.ival)
return;
Cbuf_AddText(va("say server is %s\n", NET_AdrToString(adr, sizeof(adr), &cls.netchan.remote_address)), RESTRICT_LOCAL);
}
static void Validation_Skins(void)
{
extern cvar_t r_fullbrightSkins, r_fb_models, ruleset_allow_fbmodels;
int percent = r_fullbrightSkins.value*100;
if (!allow_f_skins.ival)
return;
RulesetLatch(&r_fb_models);
RulesetLatch(&r_fullbrightSkins);
if (percent < 0)
percent = 0;
if (percent > cls.allow_fbskins*100)
percent = cls.allow_fbskins*100;
if (percent)
Cbuf_AddText(va("say all player skins %i%% fullbright%s\n", percent, (r_fb_models.ival == 1 && ruleset_allow_fbmodels.ival)?" (non-player 100%%)":(r_fb_models.value?" (plus luma)":"")), RESTRICT_LOCAL);
else if (r_fb_models.ival == 1 && ruleset_allow_fbmodels.ival)
Cbuf_AddText("say non-player entities glow in the dark like a bright big cheat\n", RESTRICT_LOCAL);
else if (r_fb_models.ival)
Cbuf_AddText("say luma textures only\n", RESTRICT_LOCAL);
else
Cbuf_AddText("say Only cheaters use full bright skins\n", RESTRICT_LOCAL);
}
static void Validation_Scripts(void)
{ //subset of ruleset
if (!allow_f_scripts.ival)
return;
if (ruleset_allow_frj.ival)
Cbuf_AddText("say scripts are allowed\n", RESTRICT_LOCAL);
else
Cbuf_AddText("say scripts are capped\n", RESTRICT_LOCAL);
}
static void Validation_FakeShaft(void)
{
extern cvar_t cl_truelightning;
if (!allow_f_fakeshaft.ival)
return;
if (cl_truelightning.value > 0.999)
Cbuf_AddText("say fakeshaft on\n", RESTRICT_LOCAL);
else if (cl_truelightning.value > 0)
Cbuf_AddText(va("say fakeshaft %.1f%%\n", cl_truelightning.value), RESTRICT_LOCAL);
else
Cbuf_AddText("say fakeshaft off\n", RESTRICT_LOCAL);
}
static void Validation_System(void)
{ //subset of ruleset
if (!allow_f_system.ival)
return;
Cbuf_AddText("say f_system not supported\n", RESTRICT_LOCAL);
}
static void Validation_CmdLine(void)
{
if (!allow_f_cmdline.ival)
return;
Cbuf_AddText("say f_cmdline not supported\n", RESTRICT_LOCAL);
}
//////////////////////
//rulesets
typedef struct {
char *rulename;
char *rulevalue;
} rulesetrule_t;
typedef struct {
char *rulesetname;
rulesetrule_t *rule;
qboolean flagged;
} ruleset_t;
rulesetrule_t rulesetrules_strict[] = {
{"ruleset_allow_shaders", "0"}, /*users can potentially create all sorts of wallhacks or spiked models with this*/
{"ruleset_allow_watervis", "0"}, /*oh noes! users might be able to see underwater if they're already in said water. oh wait. what? why do we care, dude*/
{"r_vertexlight", "0"},
{"ruleset_allow_playercount", "0"},
{"ruleset_allow_frj", "0"},
{"ruleset_allow_packet", "0"},
{"ruleset_allow_particle_lightning", "0"},
{"ruleset_allow_overlong_sounds", "0"},
{"ruleset_allow_larger_models", "0"},
{"ruleset_allow_modified_eyes", "0"},
{"ruleset_allow_sensitive_texture_replacements", "0"},
{"ruleset_allow_localvolume", "0"},
{"ruleset_allow_fbmodels", "0"},
{"scr_autoid_team", "0"}, /*sort of a wallhack*/
{"tp_disputablemacros", "0"},
{"cl_instantrotate", "0"},
{"v_projectionmode", "0"}, /*no extended fovs*/
{"r_shadow_realtime_world", "0"}, /*static lighting can be used to cast shadows around corners*/
{"ruleset_allow_in", "0"},
{"r_projection", "0"},
{"gl_shadeq1_name", "*"},
{NULL}
};
rulesetrule_t rulesetrules_nqr[] = {
{"ruleset_allow_larger_models", "0"},
{"ruleset_allow_overlong_sounds", "0"},
{"ruleset_allow_particle_lightning", "0"},
{"ruleset_allow_packet", "0"},
{"ruleset_allow_frj", "0"},
{"ruleset_allow_modified_eyes", "0"},
{"ruleset_allow_sensitive_texture_replacements", "0"},
{"ruleset_allow_localvolume", "0"},
{"ruleset_allow_shaders", "0"},
{"ruleset_allow_fbmodels", "0"},
{"r_vertexlight", "0"},
{"v_projectionmode", "0"},
{"sbar_teamstatus", "0"},
{"ruleset_allow_in", "0"},
{"r_projection", "0"},
{"gl_shadeq1_name", "*"},
{NULL}
};
static ruleset_t rulesets[] =
{
{"strict", rulesetrules_strict},
{"nqr", rulesetrules_nqr},
//{"eql", rulesetrules_nqr},
{NULL}
};
static qboolean ruleset_locked;
void RulesetLatch(cvar_t *cvar)
{
cvar->flags |= CVAR_RULESETLATCH;
}
void Validation_DelatchRulesets(void)
{ //game has come to an end, allow the ruleset to be changed
ruleset_locked = false;
if (Cvar_ApplyLatches(CVAR_RULESETLATCH))
Con_DPrintf("Ruleset deactivated\n");
}
qboolean Validation_GetCurrentRulesetName(char *rsnames, int resultbuflen, qboolean enforcechosenrulesets)
{ //this code is more complex than it needs to be
//this allows for the ruleset code to print a ruleset name that is applied via the cvars, but not directly named by the user
cvar_t *var;
ruleset_t *rs;
int i;
if (enforcechosenrulesets)
ruleset_locked = true;
rs = rulesets;
*rsnames = '\0';
for (rs = rulesets; rs->rulesetname; rs++)
{
rs->flagged = false;
for (i = 0; rs->rule[i].rulename; i++)
{
var = Cvar_FindVar(rs->rule[i].rulename);
if (!var) //sw rendering?
continue;
if (strcmp(var->string, rs->rule[i].rulevalue))
{
Con_DPrintf("ruleset \"%s\" requires \"%s\" to be \"%s\"\n", rs->rulesetname, rs->rule[i].rulename, rs->rule[i].rulevalue);
break; //current settings don't match
}
}
if (!rs->rule[i].rulename)
{
if (*rsnames)
{
Q_strncatz(rsnames, ", ", resultbuflen);
}
Q_strncatz(rsnames, rs->rulesetname, resultbuflen);
rs->flagged = true;
}
}
if (*rsnames)
{
//as we'll be telling the other players what rules we're playing by, we'd best stick to them
if (enforcechosenrulesets)
{
for (rs = rulesets; rs->rulesetname; rs++)
{
if (!rs->flagged)
continue;
for (i = 0; rs->rule[i].rulename; i++)
{
var = Cvar_FindVar(rs->rule[i].rulename);
if (!var)
continue;
RulesetLatch(var); //set the latched flag
}
}
}
return true;
}
else
return false;
}
void Validation_OldRuleset(void)
{
char rsnames[1024];
if (Validation_GetCurrentRulesetName(rsnames, sizeof(rsnames), true))
Cbuf_AddText(va("say Ruleset: %s\n", rsnames), RESTRICT_LOCAL);
else
Cbuf_AddText("say No specific ruleset\n", RESTRICT_LOCAL);
}
void Validation_AllChecks(void)
{
char servername[22];
char playername[16];
char *enginebuild = version_string();
char localpnamelen = strlen(cl.players[cl.playerview[0].playernum].name);
char ruleset[1024];
//figure out the padding for the player's name.
if (localpnamelen >= 15)
playername[0] = 0;
else
{
//pad the left side to compensate for the player name prefix the server will add in the final svc_print
memset(playername, ' ', 15-localpnamelen);
playername[15-localpnamelen] = 0;
}
//get the current server address
NET_AdrToString(servername, sizeof(servername), &cls.netchan.remote_address);
//get the ruleset names
if (!Validation_GetCurrentRulesetName(ruleset, sizeof(ruleset), true))
Q_strncpyz(ruleset, "no ruleset", sizeof(ruleset));
//now send it
CL_SendClientCommand(true, "say \"%s%21s " "%16s %s\"", playername, servername, enginebuild, ruleset);
}
void Validation_Apply_Ruleset(void)
{ //rulesets are applied when the client first gets a connection to the server
ruleset_t *rs;
rulesetrule_t *rule;
cvar_t *var;
int i;
char *rulesetname = ruleset.string;
if (ruleset_locked)
{
if (ruleset.modified)
{
Con_Printf("Cannot change rulesets after the current ruleset has been announced\n");
ruleset.modified = false;
}
return;
}
ruleset.modified = false;
if (!strcmp(rulesetname, "smackdown")) //officially, smackdown cannot authorise this, thus we do not use that name. however, imported configs tend to piss people off.
rulesetname = "strict";
if (!*rulesetname || !strcmp(rulesetname, "none") || !strcmp(rulesetname, "default"))
{
if (Cvar_ApplyLatches(CVAR_RULESETLATCH))
Con_DPrintf("Ruleset deactivated\n");
return; //no ruleset is set
}
for (rs = rulesets; rs->rulesetname; rs++)
{
if (!stricmp(rs->rulesetname, rulesetname))
break;
}
if (!rs->rulesetname)
{
Con_Printf("Cannot apply ruleset %s - not recognised\n", rulesetname);
if (Cvar_ApplyLatches(CVAR_RULESETLATCH))
Con_DPrintf("Ruleset deactivated\n");
return;
}
for (rule = rs->rule; rule->rulename; rule++)
{
for (i = 0; rs->rule[i].rulename; i++)
{
var = Cvar_FindVar(rs->rule[i].rulename);
if (!var)
continue;
if (!Cvar_ApplyLatchFlag(var, rs->rule[i].rulevalue, CVAR_RULESETLATCH))
{
Con_Printf("Failed to apply ruleset %s due to cvar %s\n", rs->rulesetname, var->name);
break;
}
}
}
Con_DPrintf("Ruleset set to %s\n", rs->rulesetname);
}
//////////////////////
void Validation_Auto_Response(int playernum, char *s)
{
static float versionresponsetime;
static float modifiedresponsetime;
static float skinsresponsetime;
static float serverresponsetime;
static float rulesetresponsetime;
static float systemresponsetime;
static float fakeshaftresponsetime;
static float cmdlineresponsetime;
static float scriptsresponsetime;
//quakeworld tends to use f_*
//netquake uses the slightly more guessable q_* form
if (!strncmp(s, "f_", 2))
s+=2;
else if (!strncmp(s, "q_", 2))
s+=2;
else
return;
if (!strncmp(s, "version", 7) && versionresponsetime < Sys_DoubleTime()) //respond to it.
{
Validation_Version();
versionresponsetime = Sys_DoubleTime() + 5;
}
else if (cl.spectator)
return;
else if (!strncmp(s, "server", 6) && serverresponsetime < Sys_DoubleTime()) //respond to it.
{
Validation_Server();
serverresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "system", 6) && systemresponsetime < Sys_DoubleTime())
{
Validation_System();
systemresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "cmdline", 7) && cmdlineresponsetime < Sys_DoubleTime())
{
Validation_CmdLine();
cmdlineresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "fakeshaft", 9) && fakeshaftresponsetime < Sys_DoubleTime())
{
Validation_FakeShaft();
fakeshaftresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "modified", 8) && modifiedresponsetime < Sys_DoubleTime()) //respond to it.
{
Validation_FilesModified();
modifiedresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "scripts", 7) && scriptsresponsetime < Sys_DoubleTime())
{
Validation_Scripts();
scriptsresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "skins", 5) && skinsresponsetime < Sys_DoubleTime()) //respond to it.
{
Validation_Skins();
skinsresponsetime = Sys_DoubleTime() + 5;
}
else if (!strncmp(s, "ruleset", 7) && rulesetresponsetime < Sys_DoubleTime())
{
if (1)
Validation_AllChecks();
else
Validation_OldRuleset();
rulesetresponsetime = Sys_DoubleTime() + 5;
}
}