b380421e42
Clarify the 'too many alternate clients' messages. Gamepad movement should respond to +speed. favour fte's nq protocols over dp ones when both are supported by the client, to boost compat with qss. try to generate proper utf-8 filenames from player/team names when recording mvds, instead of bad char encodings. Fix loading saved games with splitscreen enabled. pr_dumpplatform can now optionally filter symbols via some specific external files, adding deprecated tags for any symbol not also supported by DP and QSS. Rework CSQC globals to use macros, to ensure completeness and consistency. rework sv_csqcdebug to use special svcs. This should make it more robust. Fix some scary warnings when vid_reloading the vulkan renderer. Fix a possible segfault from opengl+rtlights. Mousewheel will now interact better with engine menus. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5757 fc73d0e0-1445-4013-8a0c-d673dee63da5
1066 lines
29 KiB
C
1066 lines
29 KiB
C
// log.c: handles console logging functions and cvars
|
|
|
|
#include "quakedef.h"
|
|
|
|
// cvar callbacks
|
|
static void QDECL Log_Dir_Callback (struct cvar_s *var, char *oldvalue);
|
|
static void QDECL Log_Name_Callback (struct cvar_s *var, char *oldvalue);
|
|
|
|
// cvars
|
|
#define CONLOGGROUP "Console logging"
|
|
cvar_t log_enable[LOG_TYPES] = { CVARF("log_enable", "0", CVAR_NOTFROMSERVER),
|
|
CVARF("log_enable_players", "0", CVAR_NOTFROMSERVER),
|
|
CVARF("log_enable_rcon", "1", CVAR_NOTFROMSERVER)
|
|
};
|
|
cvar_t log_name[LOG_TYPES] = { CVARFC("log_name", "qconsole", CVAR_NOTFROMSERVER, Log_Name_Callback),
|
|
CVARFC("log_name_players", "players", CVAR_NOTFROMSERVER, Log_Name_Callback),
|
|
CVARFC("log_name_rcon", "rcon", CVAR_NOTFROMSERVER, Log_Name_Callback)};
|
|
cvar_t log_dir_var = CVARAFC("log_dir", "", "sv_logdir", CVAR_NOTFROMSERVER, Log_Dir_Callback);
|
|
cvar_t log_readable = CVARFD("log_readable", "7", CVAR_NOTFROMSERVER, "Bitfield describing what to convert/strip. If 0, exact byte representation will be used.\n&1: Dequakify text.\n&2: Strip special markup.\n&4: Strip ansi control codes.");
|
|
cvar_t log_developer = CVARFD("log_developer", "0", CVAR_NOTFROMSERVER, "Enables logging of console prints when set to 1. Otherwise unimportant messages will not fill up your log files.");
|
|
cvar_t log_rotate_files = CVARF("log_rotate_files", "0", CVAR_NOTFROMSERVER);
|
|
cvar_t log_rotate_size = CVARF("log_rotate_size", "131072", CVAR_NOTFROMSERVER);
|
|
cvar_t log_timestamps = CVARF("log_timestamps", "1", CVAR_NOTFROMSERVER);
|
|
#ifdef _WIN32
|
|
cvar_t log_dosformat = CVARF("log_dosformat", "1", CVAR_NOTFROMSERVER);
|
|
#else
|
|
cvar_t log_dosformat = CVARF("log_dosformat", "0", CVAR_NOTFROMSERVER);
|
|
#endif
|
|
qboolean log_newline[LOG_TYPES];
|
|
|
|
#ifdef IPLOG
|
|
cvar_t iplog_autodump = CVARFD("ipautodump", "1", CVAR_NOTFROMSERVER, "Enables dumping the 'iplog.txt' file, which contains a log of usernames seen for a given IP, which is useful for detecting fake-nicks.");
|
|
#endif
|
|
|
|
static char log_dir[MAX_OSPATH];
|
|
static enum fs_relative log_root = FS_GAMEONLY;
|
|
|
|
|
|
// Log_Dir_Callback: called when a log_dir is changed
|
|
static void QDECL Log_Dir_Callback (struct cvar_s *var, char *oldvalue)
|
|
{
|
|
char *t = var->string;
|
|
char *e = t + (*t?strlen(t):0);
|
|
|
|
// sanity check for directory. // is equivelent to /../ on some systems, so make sure that can't be used either. : is for drives on windows or amiga, or alternative thingies on windows, so block thoses completely.
|
|
if (strstr(t, "..") || strstr(t, ":") || *t == '/' || *t == '\\' || *e == '/' || *e == '\\' || strstr(t, "//") || strstr(t, "\\\\"))
|
|
{
|
|
Con_Printf(CON_NOTICE "%s forced to default due to invalid characters.\n", var->name);
|
|
// recursion is avoided by assuming the default value is sane
|
|
Cvar_ForceSet(var, var->enginevalue);
|
|
}
|
|
|
|
if (!strncmp(var->string, "./", 2)||!strncmp(var->string, ".\\", 2))
|
|
{
|
|
strcpy(log_dir, var->string+2);
|
|
log_root = FS_ROOT;
|
|
}
|
|
else
|
|
{
|
|
strcpy(log_dir, var->string);
|
|
log_root = FS_GAMEONLY;
|
|
}
|
|
}
|
|
|
|
// Log_Name_Callback: called when a log_name is changed
|
|
static void QDECL Log_Name_Callback (struct cvar_s *var, char *oldvalue)
|
|
{
|
|
char *t = var->string;
|
|
|
|
// sanity check for directory
|
|
if (strstr(t, "..") || strstr(t, ":") || strstr(t, "/") || strstr(t, "\\"))
|
|
{
|
|
Con_Printf(CON_NOTICE "%s forced to default due to invalid characters.\n", var->name);
|
|
// recursion is avoided by assuming the default value is sane
|
|
Cvar_ForceSet(var, var->enginevalue);
|
|
}
|
|
}
|
|
|
|
// Con_Log: log string to console log
|
|
void Log_String (logtype_t lognum, const char *s)
|
|
{
|
|
vfsfile_t *fi;
|
|
char *f; // filename
|
|
char *t;
|
|
char utf8[2048];
|
|
int i;
|
|
char fbase[MAX_QPATH];
|
|
char fname[MAX_QPATH];
|
|
conchar_t cline[2048], *c;
|
|
unsigned int u, flags;
|
|
|
|
if (!log_enable[lognum].value)
|
|
return;
|
|
|
|
if (log_name[lognum].string[0])
|
|
f = log_name[lognum].string;
|
|
else
|
|
f = log_name[lognum].enginevalue;
|
|
|
|
if (!f)
|
|
return;
|
|
|
|
COM_ParseFunString(CON_WHITEMASK, s, cline, sizeof(cline), !(log_readable.ival & 2));
|
|
t = utf8;
|
|
for (c = cline; *c; )
|
|
{
|
|
c = Font_Decode(c, &flags, &u);
|
|
if ((flags & CON_HIDDEN) && (log_readable.ival & 2))
|
|
continue;
|
|
if (log_readable.ival&1)
|
|
u = COM_DeQuake(u);
|
|
|
|
//at the start of a new line, we might want a timestamp (so timestamps are correct for the first char of the line, instead of the preceeding \n)
|
|
if (log_newline[lognum])
|
|
{
|
|
if (log_timestamps.ival)
|
|
{
|
|
time_t unixtime = time(NULL);
|
|
int bufferspace = utf8+sizeof(utf8)-1-t;
|
|
if (bufferspace > 0)
|
|
{
|
|
strftime(t, bufferspace, "%Y-%m-%d %H:%M:%S ", localtime(&unixtime));
|
|
t += strlen(t);
|
|
}
|
|
}
|
|
log_newline[lognum] = false;
|
|
}
|
|
|
|
//make sure control codes are stripped. no exploiting xterm bugs please.
|
|
if ((log_readable.ival & 4) && ((u < 32 && u != '\t' && u != '\n') || u == 127 || (u >= 128 && u < 128+32))) //\r is stripped too
|
|
u = '?';
|
|
//if dos format logs, we insert a \r before every \n (also flag next char as the start of a new line)
|
|
if (u == '\n')
|
|
{
|
|
log_newline[lognum] = true;
|
|
if (log_dosformat.ival)
|
|
t += utf8_encode(t, '\r', utf8+sizeof(utf8)-1-t);
|
|
}
|
|
t += utf8_encode(t, u, utf8+sizeof(utf8)-1-t);
|
|
}
|
|
*t = 0;
|
|
|
|
if (*log_dir)
|
|
Q_snprintfz(fbase, sizeof(fname)-4, "%s/%s", log_dir, f);
|
|
else
|
|
Q_snprintfz(fbase, sizeof(fname)-4, "%s", f);
|
|
Q_snprintfz(fname, sizeof(fname), "%s.log", fbase);
|
|
|
|
// file rotation
|
|
if (log_rotate_size.value >= 4096 && log_rotate_files.value >= 1)
|
|
{
|
|
int x;
|
|
vfsfile_t *fi;
|
|
|
|
// check file size, use x as temp
|
|
if ((fi = FS_OpenVFS(fname, "rb", log_root)))
|
|
{
|
|
x = VFS_GETLEN(fi);
|
|
VFS_CLOSE(fi);
|
|
x += strlen(utf8); // add string size to file size to never go over
|
|
}
|
|
else
|
|
x = 0;
|
|
|
|
if (x > (int)log_rotate_size.value)
|
|
{
|
|
char newf[MAX_QPATH];
|
|
char oldf[MAX_QPATH];
|
|
|
|
i = log_rotate_files.value;
|
|
|
|
// unlink file at the top of the chain
|
|
Q_snprintfz(oldf, sizeof(oldf), "%s.%i.log", fbase, i);
|
|
FS_Remove(oldf, log_root);
|
|
|
|
// rename files through chain
|
|
for (x = i-1; x > 0; x--)
|
|
{
|
|
strcpy(newf, oldf);
|
|
Q_snprintfz(oldf, sizeof(oldf), "%s.%i.log", fbase, x);
|
|
|
|
// check if file exists, otherwise skip
|
|
if ((fi = FS_OpenVFS(oldf, "rb", log_root)))
|
|
VFS_CLOSE(fi);
|
|
else
|
|
continue; // skip nonexistant files
|
|
|
|
if (!FS_Rename(oldf, newf, log_root))
|
|
{
|
|
// rename failed, disable log and bug out
|
|
Cvar_ForceSet(&log_enable[lognum], "0");
|
|
Con_Printf("Unable to rotate log files. Logging disabled.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO: option to compress file somewhere in here?
|
|
// rename our base file, which had better exist...
|
|
if (!FS_Rename(fname, oldf, log_root))
|
|
{
|
|
// rename failed, disable log and bug out
|
|
Cvar_ForceSet(&log_enable[lognum], "0");
|
|
Con_Printf("Unable to rename base log file. Logging disabled.\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
FS_CreatePath(fname, log_root);
|
|
if ((fi = FS_OpenVFS(fname, "ab", log_root)))
|
|
{
|
|
VFS_WRITE(fi, utf8, strlen(utf8));
|
|
VFS_CLOSE(fi);
|
|
}
|
|
else
|
|
{
|
|
// write failed, bug out
|
|
Cvar_ForceSet(&log_enable[lognum], "0");
|
|
Con_Printf("Unable to write to log file. Logging disabled.\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Con_Log (const char *s)
|
|
{
|
|
Log_String(LOG_CONSOLE, s);
|
|
}
|
|
|
|
|
|
#ifndef CLIENTONLY
|
|
//still to add stuff at:
|
|
//connects
|
|
//disconnects
|
|
//kicked
|
|
void SV_LogPlayer(client_t *cl, char *msg)
|
|
{
|
|
char line[2048];
|
|
char remote_adr[MAX_ADR_SIZE];
|
|
char realip_adr[MAX_ADR_SIZE];
|
|
|
|
if (cl->protocol == SCP_BAD)
|
|
return; //don't log botclients
|
|
|
|
Q_snprintfz(line, sizeof(line)-1,
|
|
"%s\\%s\\%i\\%s\\%s\\%i\\guid\\%s",
|
|
msg, cl->name, cl->userid,
|
|
NET_BaseAdrToString(remote_adr, sizeof(remote_adr), &cl->netchan.remote_address), (cl->realip_status > 0 ? NET_BaseAdrToString(realip_adr, sizeof(realip_adr), &cl->realip) : "??"),
|
|
cl->netchan.remote_address.port, cl->guid);
|
|
InfoBuf_ToString(&cl->userinfo, line+strlen(line), sizeof(line)-1-strlen(line), NULL, NULL, NULL, NULL, NULL);
|
|
Q_strncatz(line, "\n", sizeof(line));
|
|
|
|
Log_String(LOG_PLAYER, line);
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef HAVE_LEGACY
|
|
static struct {
|
|
const char *commandname;
|
|
const char *desc;
|
|
} legacylog[] =
|
|
{
|
|
{"logfile", ""},
|
|
{"logplayers", " players"},
|
|
{"logrcon", " frags"},
|
|
};
|
|
|
|
void Log_Logfile_f (void)
|
|
{
|
|
size_t logtype;
|
|
const char *cmd = Cmd_Argv(0);
|
|
for (logtype = 0; logtype < countof(legacylog); logtype++)
|
|
if (!Q_strcasecmp(legacylog[logtype].commandname, cmd))
|
|
break;
|
|
|
|
if (log_enable[logtype].value)
|
|
{
|
|
Cvar_SetValue(&log_enable[logtype], 0);
|
|
Con_Printf("Logging%s disabled.\n", legacylog[logtype].desc);
|
|
}
|
|
else
|
|
{
|
|
const char *f;
|
|
char syspath[MAX_OSPATH];
|
|
|
|
if (log_name[logtype].string[0])
|
|
f = log_name[logtype].string;
|
|
else
|
|
f = log_name[logtype].enginevalue;
|
|
|
|
if (*log_dir)
|
|
f = va("%s/%s.log", log_dir, f);
|
|
else
|
|
f = va("%s.log", f);
|
|
|
|
if (FS_NativePath(f, log_root, syspath, sizeof(syspath)))
|
|
Con_Printf("%s", va("Logging%s to %s\n", legacylog[logtype].desc, syspath));
|
|
else
|
|
Con_Printf("%s", va("Logging%s to %s\n", legacylog[logtype].desc, f));
|
|
Cvar_SetValue(&log_enable[logtype], 1);
|
|
}
|
|
|
|
}
|
|
/*
|
|
void SV_Fraglogfile_f (void)
|
|
{
|
|
char name[MAX_QPATH];
|
|
int i;
|
|
|
|
if (sv_fraglogfile)
|
|
{
|
|
Con_TPrintf ("Frag file logging off.\n");
|
|
VFS_CLOSE (sv_fraglogfile);
|
|
sv_fraglogfile = NULL;
|
|
return;
|
|
}
|
|
|
|
// find an unused name
|
|
for (i=0 ; i<1000 ; i++)
|
|
{
|
|
sprintf (name, "frag_%i.log", i);
|
|
sv_fraglogfile = FS_OpenVFS(name, "rb", FS_GAME);
|
|
if (!sv_fraglogfile)
|
|
{ // can't read it, so create this one
|
|
sv_fraglogfile = FS_OpenVFS (name, "wb", FS_GAME);
|
|
if (!sv_fraglogfile)
|
|
i=1000; // give error
|
|
break;
|
|
}
|
|
VFS_CLOSE (sv_fraglogfile);
|
|
}
|
|
if (i==1000)
|
|
{
|
|
Con_TPrintf ("Can't open any logfiles.\n");
|
|
sv_fraglogfile = NULL;
|
|
return;
|
|
}
|
|
|
|
Con_TPrintf ("Logging frags to %s.\n", name);
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
#ifdef IPLOG
|
|
/*for fuck sake, why can people still not write simple files. proquake is writing binary files as text ones. this function is to try to deal with that fuckup*/
|
|
static size_t IPLog_Read_Fucked(qbyte *file, size_t *offset, size_t totalsize, qbyte *out, size_t outsize)
|
|
{
|
|
size_t read = 0;
|
|
while (outsize-- > 0 && *offset < totalsize)
|
|
{
|
|
if (file[*offset] == '\r' && *offset+1 < totalsize && file[*offset+1] == '\n')
|
|
{
|
|
out[read] = '\n';
|
|
*offset += 2;
|
|
read += 1;
|
|
}
|
|
else
|
|
{
|
|
out[read] = file[*offset];
|
|
*offset += 1;
|
|
read += 1;
|
|
}
|
|
}
|
|
return read;
|
|
}
|
|
/*need to make sure any 13 bytes are followed by 10s so that we don't bug out when read back in *sigh* */
|
|
static size_t IPLog_Write_Fucked(vfsfile_t *file, qbyte *out, size_t outsize)
|
|
{
|
|
qbyte tmp[64];
|
|
size_t write = 0;
|
|
size_t block = 0;
|
|
while (outsize-- > 0)
|
|
{
|
|
if (block >= sizeof(tmp)-4)
|
|
{
|
|
VFS_WRITE(file, tmp, block);
|
|
write += block;
|
|
block = 0;
|
|
}
|
|
if (*out == '\n')
|
|
tmp[block++] = '\r';
|
|
tmp[block++] = *out++;
|
|
}
|
|
if (block)
|
|
{
|
|
VFS_WRITE(file, tmp, block);
|
|
write += block;
|
|
}
|
|
return write;
|
|
}
|
|
qboolean IPLog_Merge_File(const char *fname)
|
|
{
|
|
char ip[MAX_ADR_SIZE];
|
|
char name[256];
|
|
char line[1024];
|
|
vfsfile_t *f;
|
|
if (!*fname)
|
|
fname = "iplog.txt";
|
|
f = FS_OpenVFS(fname, "rb", FS_PUBBASEGAMEONLY);
|
|
if (!f)
|
|
f = FS_OpenVFS(fname, "rb", FS_GAME);
|
|
if (!f)
|
|
return false;
|
|
if (!Q_strcasecmp(COM_FileExtension(fname, name, sizeof(name)), "dat"))
|
|
{ //we don't write this format because of it being limited to ipv4, as well as player name lengths
|
|
size_t l = VFS_GETLEN(f), offset = 0;
|
|
qbyte *ffs = malloc(l+1);
|
|
VFS_READ(f, ffs, l);
|
|
ffs[l] = 0;
|
|
while (IPLog_Read_Fucked(ffs, &offset, l, line, 20) == 20)
|
|
{ //yes, these addresses are weird.
|
|
Q_snprintfz(ip, sizeof(ip), "%i.%i.%i.xxx", (qbyte)line[2], (qbyte)line[1], (qbyte)line[0]);
|
|
memcpy(name, line+4, 20-4);
|
|
name[20-4] = 0;
|
|
IPLog_Add(ip, name);
|
|
}
|
|
free(ffs);
|
|
}
|
|
else
|
|
{
|
|
while (VFS_GETS(f, line, sizeof(line)-1))
|
|
{
|
|
//whether the name contains quotes or what is an awkward one.
|
|
//we always write quotes (including string markup to avoid issues)
|
|
//dp doesn't, and our parser is lazy, so its possible we'll get gibberish that way
|
|
if (COM_ParseOut(COM_ParseOut(line, ip, sizeof(ip)), name, sizeof(name)))
|
|
IPLog_Add(ip, name);
|
|
}
|
|
}
|
|
VFS_CLOSE(f);
|
|
return true;
|
|
}
|
|
struct iplog_entry
|
|
{
|
|
netadr_t adr;
|
|
netadr_t mask;
|
|
char name[1];
|
|
} **iplog_entries;
|
|
size_t iplog_num, iplog_max;
|
|
void IPLog_Add(const char *ipstr, const char *name)
|
|
{
|
|
size_t i;
|
|
netadr_t a, m;
|
|
while (*ipstr == ' ' || *ipstr == '\t')
|
|
ipstr++;
|
|
if (*ipstr != '[' && *ipstr < '0' && *ipstr > '9')
|
|
return;
|
|
if (*ipstr == '[')
|
|
ipstr++;
|
|
//some names are dodgy.
|
|
if (!*name
|
|
//|| !Q_strcasecmp(name, /*nq default*/"player") || !Q_strcasecmp(name, /*qw default*/"unnamed")
|
|
|| !strcmp(name, /*nq fallback*/"unconnected") || !strncmp(name, "BOT:", 4))
|
|
return;
|
|
memset(&a, 0, sizeof(a));
|
|
memset(&m, 0, sizeof(m));
|
|
if (!NET_StringToAdrMasked(ipstr, false, &a, &m))
|
|
return;
|
|
//might be x.y.z.w:port
|
|
//might be x.y.z.FUCKED
|
|
//might be x.y.z.0/24
|
|
//might be [::]:port
|
|
//might be [::]/bits
|
|
//or other ways to express an ip address
|
|
|
|
//FIXME: ignore private addresses?
|
|
|
|
//check for dupes
|
|
for (i = 0; i < iplog_num; i++)
|
|
{
|
|
if (!memcmp(&a, &iplog_entries[i]->adr, sizeof(netadr_t)) && !memcmp(&m, &iplog_entries[i]->mask, sizeof(netadr_t)) && !Q_strcasecmp(name, iplog_entries[i]->name))
|
|
return;
|
|
}
|
|
|
|
//looks like its new...
|
|
if (iplog_num == iplog_max)
|
|
Z_ReallocElements((void**)&iplog_entries, &iplog_max, iplog_max+64, sizeof(*iplog_entries));
|
|
iplog_entries[iplog_num] = BZ_Malloc(sizeof(struct iplog_entry) + strlen(name));
|
|
iplog_entries[iplog_num]->adr = a;
|
|
iplog_entries[iplog_num]->mask = m;
|
|
strcpy(iplog_entries[iplog_num]->name, name);
|
|
iplog_num++;
|
|
}
|
|
static void IPLog_Identify(netadr_t *adr, netadr_t *mask, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
qboolean found = false;
|
|
char line[256];
|
|
size_t i;
|
|
|
|
va_start(argptr, fmt);
|
|
vsnprintf(line, sizeof(line), fmt, argptr);
|
|
va_end(argptr);
|
|
Con_Printf("%s: ", line);
|
|
|
|
for (i = 0; i < iplog_num; i++)
|
|
{
|
|
if (NET_CompareAdrMasked(adr, &iplog_entries[i]->adr, mask?mask:&iplog_entries[i]->mask))
|
|
{
|
|
if (found)
|
|
Con_Printf(", ");
|
|
found=true;
|
|
Con_Printf("%s", iplog_entries[i]->name);
|
|
}
|
|
}
|
|
if (!found)
|
|
Con_Printf("<no matches>");
|
|
Con_Printf("\n");
|
|
}
|
|
#include "cl_ignore.h"
|
|
static void IPLog_Identify_f(void)
|
|
{
|
|
const char *nameorip = Cmd_Argv(1);
|
|
netadr_t adr, mask;
|
|
char clean[256];
|
|
char *endofnum;
|
|
strtoul(nameorip, &endofnum, 10);
|
|
|
|
if (*endofnum && NET_StringToAdrMasked (nameorip, false, &adr, &mask))
|
|
{ //if not a single number, try to parse as an ip
|
|
//treading carefully here, to avoid dns name lookups weirding everything out.
|
|
IPLog_Identify(&adr, &mask, "Identity of %s", NET_AdrToStringMasked(clean, sizeof(clean), &adr, &mask));
|
|
}
|
|
#ifdef HAVE_SERVER
|
|
else if (sv.active)
|
|
{ //if server is active, walk players to see if there's a name match to get their address and guess an address mask
|
|
client_t *cl;
|
|
int clnum = -1;
|
|
while((cl = SV_GetClientForString(nameorip, &clnum)))
|
|
{
|
|
if (cl->realip_status)
|
|
{
|
|
IPLog_Identify(&cl->realip, NULL, "Identity of %s (real) [%s]", cl->name, NET_AdrToString(clean, sizeof(clean), &cl->realip));
|
|
IPLog_Identify(&cl->netchan.remote_address, NULL, "Identity of %s (proxy) [%s]", cl->name, NET_AdrToString(clean, sizeof(clean), &cl->realip));
|
|
}
|
|
else
|
|
IPLog_Identify(&cl->netchan.remote_address, NULL, "Identity of %s [%s]", cl->name, NET_AdrToString(clean, sizeof(clean), &cl->realip));
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef HAVE_CLIENT
|
|
else if (cls.state >= ca_connected)
|
|
{ //else if client is active, walk players to see if there's a name match, to get their address+mask if known via nq hacks
|
|
int slot;
|
|
netadr_t adr;
|
|
if ((slot = Player_StringtoSlot(nameorip)) < 0)
|
|
Con_Printf("%s: no player with userid %s\n", Cmd_Argv(0), nameorip);
|
|
else if (!*cl.players[slot].ip)
|
|
Con_Printf("%s: ip address of %s is not known\n", Cmd_Argv(0), cl.players[slot].name);
|
|
else
|
|
{
|
|
if (NET_StringToAdrMasked(cl.players[slot].ip, false, &adr, &mask))
|
|
IPLog_Identify(&adr, &mask, "Identity of %s [%s]", cl.players[slot].name, cl.players[slot].ip);
|
|
else
|
|
Con_Printf("ip address of %s not known, cannot identify\n", cl.players[slot].name);
|
|
}
|
|
}
|
|
#endif
|
|
else
|
|
Con_Printf("%s: not connected, nor raw address\n", Cmd_Argv(0));
|
|
}
|
|
static int IPLog_Dump(const char *fname)
|
|
{
|
|
size_t i;
|
|
vfsfile_t *f;
|
|
qbyte line[20];
|
|
if (!*fname)
|
|
fname = "iplog.txt";
|
|
|
|
if (!iplog_num && !COM_FCheckExists(fname))
|
|
return 2; //no entries, nothing to overwrite
|
|
|
|
f = FS_OpenVFS(fname, "wb", FS_PUBBASEGAMEONLY);
|
|
if (!f)
|
|
return false;
|
|
if (!Q_strcasecmp(COM_FileExtension(fname, line, sizeof(line)), "dat"))
|
|
{
|
|
for (i = 0; i < iplog_num; i++)
|
|
{
|
|
//this shitty format supports only ipv4.
|
|
if (iplog_entries[i]->adr.type != NA_IP)
|
|
continue;
|
|
line[0] = iplog_entries[i]->adr.address.ip[2];
|
|
line[1] = iplog_entries[i]->adr.address.ip[1];
|
|
line[2] = iplog_entries[i]->adr.address.ip[0];
|
|
line[3] = 0;
|
|
strncpy(line+4, iplog_entries[i]->name, sizeof(line)-4);
|
|
IPLog_Write_Fucked(f, line, sizeof(line)); //convert \n to \r\n, to avoid fucking up any formatting with binary data (inside the address part, so *.13.10.* won't corrupt the file)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VFS_PRINTF(f, "//generated by "FULLENGINENAME"\n");
|
|
for (i = 0; i < iplog_num; i++)
|
|
{
|
|
char ip[512];
|
|
char buf[1024];
|
|
char buf2[1024];
|
|
VFS_PRINTF(f, log_dosformat.value?"%s %s\r\n":"%s %s\n", COM_QuotedString(NET_AdrToStringMasked(ip, sizeof(ip), &iplog_entries[i]->adr, &iplog_entries[i]->mask), buf2, sizeof(buf2), false), COM_QuotedString(iplog_entries[i]->name, buf, sizeof(buf), false));
|
|
}
|
|
}
|
|
VFS_CLOSE(f);
|
|
return true;
|
|
}
|
|
static void IPLog_Dump_f(void)
|
|
{
|
|
char native[MAX_OSPATH];
|
|
const char *fname = Cmd_Argv(1);
|
|
if (FS_NativePath(fname, FS_GAMEONLY, native, sizeof(native)))
|
|
Q_strncpyz(native, fname, sizeof(native));
|
|
IPLog_Merge_File(fname); //merge from the existing file, so that we're hopefully more robust if multiple processes are poking the same file.
|
|
switch (IPLog_Dump(fname))
|
|
{
|
|
case 0:
|
|
Con_Printf("unable to write %s\n", fname);
|
|
break;
|
|
default:
|
|
case 1:
|
|
Con_Printf("wrote %s\n", native);
|
|
break;
|
|
case 2:
|
|
Con_Printf("nothing to write\n");
|
|
break;
|
|
}
|
|
}
|
|
static void IPLog_Merge_f(void)
|
|
{
|
|
const char *fname = Cmd_Argv(1);
|
|
if (!IPLog_Merge_File(fname))
|
|
Con_Printf("unable to read iplog \"%s\" for merging\n", fname);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_CLIENT //requires UI prompts
|
|
struct certlog_s
|
|
{
|
|
link_t l;
|
|
char *hostname;
|
|
size_t certsize;
|
|
qbyte cert[1];
|
|
};
|
|
#define CERTLOG_FILENAME "knowncerts.txt"
|
|
static link_t certlog;
|
|
static qboolean certlog_inited = false;
|
|
static void CertLog_Import(const char *filename);
|
|
static struct certlog_s *CertLog_Find(const char *hostname)
|
|
{
|
|
struct certlog_s *l;
|
|
for (l = (struct certlog_s*)certlog.next ; l != (struct certlog_s*)&certlog ; l = (struct certlog_s*)l->l.next)
|
|
{
|
|
if (!strcmp(l->hostname, hostname))
|
|
return l;
|
|
}
|
|
return NULL;
|
|
}
|
|
static void CertLog_Update(const char *hostname, const void *cert, size_t certsize)
|
|
{
|
|
struct certlog_s *l = CertLog_Find(hostname);
|
|
if (l)
|
|
{
|
|
RemoveLink(&l->l);
|
|
Z_Free(l);
|
|
}
|
|
l = Z_Malloc(sizeof(*l) + certsize + strlen(hostname));
|
|
l->certsize = certsize;
|
|
l->hostname = l->cert + l->certsize;
|
|
memcpy(l->cert, cert, certsize);
|
|
strcpy(l->hostname, hostname);
|
|
InsertLinkAfter(&l->l, &certlog);
|
|
}
|
|
static void CertLog_Write(void)
|
|
{
|
|
struct certlog_s *l;
|
|
vfsfile_t *f = FS_OpenVFS(CERTLOG_FILENAME, "wb", FS_ROOT);
|
|
if (f)
|
|
{
|
|
VFS_PRINTF(f, "version 1.0\n");
|
|
|
|
for (l=(struct certlog_s*)certlog.next ; l != (struct certlog_s*)&certlog ; l = (struct certlog_s*)l->l.next)
|
|
{
|
|
char certhex[32768];
|
|
size_t i;
|
|
const char *hex = "0123456789abcdef";
|
|
for (i = 0; i < l->certsize; i++)
|
|
{
|
|
certhex[i*2+0] = hex[l->cert[i]>>4];
|
|
certhex[i*2+1] = hex[l->cert[i]&0xf];
|
|
}
|
|
certhex[i*2] = 0;
|
|
VFS_PRINTF(f, "%s \"", l->hostname);
|
|
VFS_PUTS(f, certhex);
|
|
VFS_PRINTF(f, "\"\n");
|
|
}
|
|
}
|
|
}
|
|
static void CertLog_Purge(void)
|
|
{
|
|
while (certlog.next != &certlog)
|
|
{
|
|
struct certlog_s *l = (struct certlog_s*)certlog.next;
|
|
RemoveLink(&l->l);
|
|
Z_Free(l);
|
|
}
|
|
|
|
certlog_inited = false;
|
|
}
|
|
static int hexdecode(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return 0;
|
|
}
|
|
static void CertLog_Import(const char *filename)
|
|
{
|
|
char addressstring[512];
|
|
char certhex[32768];
|
|
char certdata[16384];
|
|
char line[65536], *l;
|
|
size_t i, certsize;
|
|
vfsfile_t *f;
|
|
if (!certlog_inited && filename)
|
|
CertLog_Import(NULL);
|
|
certlog_inited |= !filename;
|
|
f = FS_OpenVFS(filename?filename:CERTLOG_FILENAME, "rb", FS_ROOT);
|
|
if (!f)
|
|
return;
|
|
//CertLog_Purge();
|
|
VFS_GETS(f, line, sizeof(line));
|
|
if (strncmp(line, "version 1.", 10))
|
|
return; //unsupported...
|
|
while (VFS_GETS(f, line, sizeof(line)))
|
|
{
|
|
l = line;
|
|
l = COM_ParseOut(l, addressstring, sizeof(addressstring));
|
|
l = COM_ParseOut(l, certhex, sizeof(certhex));
|
|
|
|
certsize = 0;
|
|
for (i = 0; certsize < sizeof(certdata); i++)
|
|
{
|
|
if (!certhex[(i<<1)+0] || !certhex[(i<<1)+1])
|
|
break;
|
|
certdata[certsize++] = (hexdecode(certhex[(i<<1)+0])<<4)|hexdecode(certhex[(i<<1)+1]);
|
|
}
|
|
CertLog_Update(addressstring, certdata, certsize);
|
|
}
|
|
}
|
|
static void CertLog_UntrustAll_f(void)
|
|
{
|
|
CertLog_Purge();
|
|
}
|
|
static void CertLog_Import_f(void)
|
|
{
|
|
const char *fname = Cmd_Argv(1);
|
|
if (Cmd_IsInsecure())
|
|
return;
|
|
if (!*fname)
|
|
fname = NULL;
|
|
CertLog_Import(fname);
|
|
}
|
|
struct certprompt_s
|
|
{
|
|
char *hostname;
|
|
|
|
size_t certsize;
|
|
qbyte cert[1];
|
|
};
|
|
static struct certprompt_s *certlog_curprompt;
|
|
static void CertLog_Add_Prompted(void *vctx, promptbutton_t button)
|
|
{
|
|
struct certprompt_s *ctx = vctx;
|
|
if (button == PROMPT_YES) //button_yes / button_left
|
|
{
|
|
CertLog_Update(ctx->hostname, ctx->cert, ctx->certsize);
|
|
CertLog_Write();
|
|
|
|
CL_BeginServerReconnect();
|
|
}
|
|
else
|
|
CL_Disconnect("Server certificate rejected");
|
|
|
|
certlog_curprompt = NULL;
|
|
}
|
|
qboolean CertLog_ConnectOkay(const char *hostname, void *cert, size_t certsize, unsigned int certlogproblems)
|
|
{
|
|
struct certlog_s *l;
|
|
|
|
if (certlog_curprompt)
|
|
return false;
|
|
COM_AssertMainThread("CertLog_ConnectOkay");
|
|
|
|
if (!certlog_inited)
|
|
CertLog_Import(NULL);
|
|
l = CertLog_Find(hostname);
|
|
|
|
if (!l || l->certsize != certsize || memcmp(l->cert, cert, certsize))
|
|
{ //new or different
|
|
if (qrenderer)
|
|
{
|
|
unsigned int i;
|
|
size_t len;
|
|
char *text;
|
|
const char *accepttext;
|
|
const char *lines[] = {
|
|
va(localtext("Certificate for %s\n"), hostname),
|
|
(certlogproblems&CERTLOG_WRONGHOST)?localtext("^1Certificate does not match host\n"):"",
|
|
((certlogproblems&(CERTLOG_MISSINGCA|CERTLOG_WRONGHOST))==CERTLOG_MISSINGCA)?localtext("^1Certificate authority is untrusted.\n"):"",
|
|
(certlogproblems&CERTLOG_EXPIRED)?localtext("^1Expired Certificate\n"):"",
|
|
l?localtext("\n^1WARNING: Certificate has changed since previously trusted."):""};
|
|
struct certprompt_s *ctx = certlog_curprompt = Z_Malloc(sizeof(*ctx)+certsize + strlen(hostname));
|
|
ctx->hostname = ctx->cert + certsize;
|
|
ctx->certsize = certsize;
|
|
memcpy(ctx->cert, cert, certsize);
|
|
strcpy(ctx->hostname, hostname);
|
|
|
|
if (l) //FIXME: show expiry info for the old cert, warn if more than a month?
|
|
accepttext = localtext("Replace");
|
|
else if (!certlogproblems)
|
|
accepttext = localtext("Pin");
|
|
else
|
|
accepttext = localtext("Trust");
|
|
|
|
for (i = 0, len = 0; i < countof(lines); i++)
|
|
len += strlen(lines[i]);
|
|
text = alloca(len+1);
|
|
for (i = 0, len = 0; i < countof(lines); i++)
|
|
{
|
|
strcpy(text+len, lines[i]);
|
|
len += strlen(lines[i]);
|
|
}
|
|
text[len] = 0;
|
|
|
|
//FIXME: display some sort of fingerprint
|
|
Menu_Prompt(CertLog_Add_Prompted, ctx, text, accepttext, NULL, localtext("Disconnect"));
|
|
}
|
|
return false; //can't connect yet...
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(HAVE_SERVER) && defined(HAVE_CLIENT)
|
|
static struct maplog_entry
|
|
{
|
|
struct maplog_entry *next;
|
|
float bestkills;
|
|
float bestsecrets;
|
|
float besttime; //updated when besttime<newtime (note: doesn't respond to user changelevels from the console...)
|
|
float fulltime; //updated when bestkills>=newkills
|
|
char name[1];
|
|
} *maplog_enties;
|
|
static void Log_MapsRead(void)
|
|
{
|
|
struct maplog_entry *m, **link = &maplog_enties;
|
|
vfsfile_t *f;
|
|
static qboolean maplog_loaded;
|
|
char line[8192], *s;
|
|
if (maplog_loaded)
|
|
return;
|
|
maplog_loaded = true;
|
|
f = FS_OpenVFS("maptimes.txt", "rb", FS_ROOT);
|
|
if (!f)
|
|
return; //no info yet.
|
|
while (VFS_GETS(f, line, sizeof(line)))
|
|
{
|
|
s = line;
|
|
s = COM_Parse(s);
|
|
m = Z_Malloc(sizeof(*m) + strlen(com_token));
|
|
strcpy(m->name, com_token);
|
|
|
|
s = COM_Parse(s);
|
|
m->besttime = atof(com_token);
|
|
s = COM_Parse(s);
|
|
m->fulltime = atof(com_token);
|
|
s = COM_Parse(s);
|
|
m->bestkills = atof(com_token);
|
|
s = COM_Parse(s);
|
|
m->bestsecrets = atof(com_token);
|
|
|
|
*link = m;
|
|
link = &m->next;
|
|
}
|
|
VFS_CLOSE(f);
|
|
}
|
|
struct maplog_entry *Log_FindMap(const char *purepackage, const char *mapname)
|
|
{
|
|
const char *name = va("%s/%s", purepackage, mapname);
|
|
struct maplog_entry *m;
|
|
Log_MapsRead();
|
|
for (m = maplog_enties; m; m = m->next)
|
|
{
|
|
if (!strcmp(m->name, name))
|
|
break;
|
|
}
|
|
return m;
|
|
}
|
|
static void Log_MapsDump(void)
|
|
{
|
|
if (maplog_enties)
|
|
{
|
|
struct maplog_entry *m;
|
|
vfsfile_t *f = FS_OpenVFS("maptimes.txt", "wbp", FS_ROOT);
|
|
if (f)
|
|
{
|
|
for(m = maplog_enties; m; m = m->next)
|
|
{
|
|
VFS_PRINTF(f, "\"%s\" %.9g %.9g %.9g %.9g\n", m->name, m->besttime, m->fulltime, m->bestkills, m->bestsecrets);
|
|
}
|
|
VFS_CLOSE(f);
|
|
}
|
|
}
|
|
}
|
|
qboolean Log_CheckMapCompletion(const char *packagename, const char *mapname, float *besttime, float *fulltime, float *bestkills, float *bestsecrets)
|
|
{
|
|
struct maplog_entry *m;
|
|
if (!packagename)
|
|
{
|
|
flocation_t loc;
|
|
if (!FS_FLocateFile(mapname, FSLF_DONTREFERENCE|FSLF_IGNORELINKS, &loc))
|
|
return false; //no idea which package, don't guess.
|
|
packagename = FS_GetRootPackagePath(&loc);
|
|
if (!packagename)
|
|
return false;
|
|
}
|
|
m = Log_FindMap(packagename, mapname);
|
|
if (m)
|
|
{
|
|
*besttime = m->besttime;
|
|
*fulltime = m->fulltime;
|
|
*bestkills = m->bestkills;
|
|
*bestsecrets = m->bestsecrets;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
void Log_MapNowCompleted(void)
|
|
{
|
|
struct maplog_entry *m;
|
|
flocation_t loc;
|
|
float kills, secrets, oldprogress, newprogress, maptime;
|
|
const char *packagename;
|
|
|
|
//don't log it if its deathmatch/coop/cheating.
|
|
extern int sv_allow_cheats;
|
|
if (deathmatch.ival || coop.ival || sv_allow_cheats == 1)
|
|
return;
|
|
|
|
if (!FS_FLocateFile(sv.world.worldmodel->name, FSLF_DONTREFERENCE|FSLF_IGNORELINKS, &loc))
|
|
{
|
|
Con_Printf("completion log: unable to determine logical path for map\n");
|
|
return; //don't know
|
|
}
|
|
packagename = FS_GetRootPackagePath(&loc);
|
|
if (!packagename)
|
|
{
|
|
Con_Printf("completion log: unable to determine logical path for map\n");
|
|
return;
|
|
}
|
|
|
|
m = Log_FindMap(packagename, sv.world.worldmodel->name);
|
|
if (!m)
|
|
{
|
|
m = Z_Malloc(sizeof(*m)+strlen(packagename)+strlen(sv.world.worldmodel->name)+2);
|
|
sprintf(m->name, "%s/%s", packagename, sv.world.worldmodel->name);
|
|
|
|
m->fulltime = m->besttime = INFINITY;
|
|
m->bestkills = m->bestsecrets = 0;
|
|
m->next = maplog_enties;
|
|
maplog_enties = m;
|
|
}
|
|
|
|
kills = pr_global_struct->killed_monsters;
|
|
secrets = pr_global_struct->found_secrets;
|
|
maptime = sv.world.physicstime;
|
|
|
|
newprogress = secrets*10+kills;
|
|
oldprogress = m->bestsecrets*10+m->bestkills;
|
|
|
|
//if they got a new time record, update.
|
|
if (maptime<m->besttime)
|
|
m->besttime = maptime;
|
|
//if they got a new kills record, update
|
|
if (newprogress > oldprogress || (newprogress==oldprogress && maptime<m->fulltime))
|
|
{
|
|
m->bestkills = kills;
|
|
m->bestsecrets = secrets;
|
|
m->fulltime = maptime;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void Log_ShutDown(void)
|
|
{
|
|
#if defined(HAVE_SERVER) && defined(HAVE_CLIENT)
|
|
Log_MapsDump();
|
|
#endif
|
|
|
|
#ifdef IPLOG
|
|
if (iplog_autodump.ival)
|
|
IPLog_Dump("iplog.txt");
|
|
// IPLog_Dump("iplog.dat");
|
|
|
|
while(iplog_num > 0)
|
|
{
|
|
iplog_num--;
|
|
BZ_Free(iplog_entries[iplog_num]);
|
|
}
|
|
BZ_Free(iplog_entries);
|
|
iplog_entries = NULL;
|
|
iplog_max = iplog_num = 0;
|
|
#endif
|
|
}
|
|
|
|
void Log_Init(void)
|
|
{
|
|
int i;
|
|
// register cvars
|
|
for (i = 0; i < LOG_TYPES; i++)
|
|
{
|
|
#ifdef CLIENTONLY
|
|
if (i != LOG_CONSOLE)
|
|
continue;
|
|
#endif
|
|
Cvar_Register (&log_enable[i], CONLOGGROUP);
|
|
Cvar_Register (&log_name[i], CONLOGGROUP);
|
|
log_newline[i] = true;
|
|
#ifdef IPLOG
|
|
Cmd_AddCommand(legacylog[i].commandname, IPLog_Merge_f);
|
|
#endif
|
|
}
|
|
Cvar_Register (&log_dir_var, CONLOGGROUP);
|
|
Cvar_Register (&log_readable, CONLOGGROUP);
|
|
Cvar_Register (&log_developer, CONLOGGROUP);
|
|
Cvar_Register (&log_rotate_size, CONLOGGROUP);
|
|
Cvar_Register (&log_rotate_files, CONLOGGROUP);
|
|
Cvar_Register (&log_dosformat, CONLOGGROUP);
|
|
Cvar_Register (&log_timestamps, CONLOGGROUP);
|
|
|
|
#ifdef IPLOG
|
|
Cmd_AddCommandD("identify", IPLog_Identify_f, "Looks up a player's ip to see if they're using a different name");
|
|
Cmd_AddCommand("ipmerge", IPLog_Merge_f);
|
|
Cmd_AddCommand("ipdump", IPLog_Dump_f);
|
|
Cvar_Register (&iplog_autodump, CONLOGGROUP);
|
|
#endif
|
|
|
|
// cmd line options, debug options
|
|
#ifdef CRAZYDEBUGGING
|
|
Cvar_ForceSet(&log_enable[LOG_CONSOLE], "1");
|
|
TRACE(("dbg: Con_Init: log_enable forced\n"));
|
|
#endif
|
|
|
|
if (COM_CheckParm("-condebug"))
|
|
Cvar_ForceSet(&log_enable[LOG_CONSOLE], "1");
|
|
|
|
#ifdef HAVE_CLIENT
|
|
ClearLink(&certlog);
|
|
Cmd_AddCommand("dtls_untrustall", CertLog_UntrustAll_f);
|
|
Cmd_AddCommand("dtls_importtrust", CertLog_Import_f);
|
|
#endif
|
|
}
|