From e86e59bfe6c3a31ec76d6dd4de22457b1a8f821a Mon Sep 17 00:00:00 2001 From: MaeJong Date: Thu, 13 Apr 2017 11:30:00 +0000 Subject: Reports system --- src/game/g_admin.c | 1644 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/game/g_admin.h | 60 ++ src/game/g_client.c | 3 + src/game/g_cmds.c | 12 +- src/game/g_local.h | 6 + src/game/g_main.c | 15 +- 6 files changed, 1699 insertions(+), 41 deletions(-) diff --git a/src/game/g_admin.c b/src/game/g_admin.c index 7bd4f0d..60779a7 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -273,48 +273,84 @@ g_admin_cmd_t g_admin_cmds[ ] = "move a player to a specified team", "[^3name|slot#^7] [^3h|a|s^7] (^3duration^7)" }, - + + {"rarclist", G_admin_rarclist, "reportmanage", + "display a (partial) list of archived reports", + "[^3from id#|player|ip range^7]" + }, + + {"rban", G_admin_rban, "reportmanage", + "ban a reported player", + "[^3report#^7] [^3time^7] [^3reason^7]" + }, + + {"rclose", G_admin_rclose, "reportmanage", + "closes a report entry, argument '!' archives it", + "[^3report#^7] (^3!^7) [^3message^7]\n" + "you must specify a message for the reporter to see" + }, + {"readconfig", G_admin_readconfig, "readconfig", "reloads the admin config file and refreshes permission flags", "" }, + {"report", G_admin_report, "report", + "report a player - provide evidence if possible", + "[^3name|slot#^7] [^3reason/additional info^7]" + }, + {"register", G_admin_register, "register", "Registers your name to protect it from being used by others or updates your admin name to your current name.", "[^3level^7] [^3password^7]" }, - + {"rename", G_admin_rename, "rename", "rename a player", "[^3name|slot#^7] [^3new name^7]" }, - + {"restart", G_admin_restart, "restart", "restart the current map (optionally using named layout or keeping/switching teams)", "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)" }, - + {"revert", G_admin_revert, "revert", "revert one or more buildlog events, optionally of only one team", "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)" "\n ^3Example:^7 '!revert x5 h' reverts the last 5 events affecting human buildables" }, - + + {"rlist", G_admin_rlist, "report", + "display a (partial) list of active reports", + "[^3from id#|player|ip range^7]" + }, + + {"rnote", G_admin_rnote, "reportmanage", + "edits the note of a report or archive entry", + "[^3report#^7] (^3!^7) [^3note^7] - ^3!^7 means archive instead of report" + }, + {"rotation", G_admin_listrotation, "rotation", "display a list of maps that are in the active map rotation", "" }, - + + {"rpurge", G_admin_rpurge, "reportpurge", + "purges a report or archive entry", + "[^3report#^7] (^3!^7) - ^3!^7 means archive instead of report" + }, + {"seen", G_admin_seen, "seen", "find the last time a player was on the server", "[^3name|admin#^7]" }, - + {"setlevel", G_admin_setlevel, "setlevel", "sets the admin level of a player", "[^3name|slot#|admin#^7] [^3level^7]" }, - + {"showbans", G_admin_showbans, "showbans", "display a (partial) list of active bans", "(^5start at ban#^7) (^5name|IP|'-subnet'^7)" @@ -411,6 +447,8 @@ static int admin_level_maxname = 0; g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ]; g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ]; g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ]; +g_admin_report_t *g_admin_reports[ MAX_ADMIN_REPORTS ]; +g_admin_archive_t *g_admin_archives[ MAX_ADMIN_ARCHIVES ]; g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ]; g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ]; @@ -735,6 +773,100 @@ static void admin_writeconfig( void ) trap_FS_Write( "\n", 1, f ); } trap_FS_FCloseFile( f ); + + if( !g_report.string[ 0 ] ) + { + G_Printf( S_COLOR_YELLOW "WARNING: g_report is not set. " + " configuration will not be saved to a file.\n" ); + return; + } + t = trap_RealTime( NULL ); + len = trap_FS_FOpenFile( g_report.string, &f, FS_WRITE ); + if( len < 0 ) + { + G_Printf( "admin_writeconfig: could not open g_report file \"%s\"\n", + g_report.string ); + return; + } + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + if( ( g_admin_reports[ i ]->expires != 0 && ( g_admin_reports[ i ]->expires - t ) < 1 ) || g_admin_reports[ i ]->closed == 2 ) + { + continue; + } + + trap_FS_Write( "[report]\n", 9, f ); + trap_FS_Write( "name = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->name, f ); + trap_FS_Write( "guid = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->guid, f ); + trap_FS_Write( "ip = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->ip, f ); + trap_FS_Write( "reason = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->reason, f ); + trap_FS_Write( "map = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->map, f ); + trap_FS_Write( "time = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->time, f ); + trap_FS_Write( "players = ", 10, f ); + admin_writeconfig_int( g_admin_reports[ i ]->players, f ); + trap_FS_Write( "admins = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->admins, f ); + trap_FS_Write( "rep = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->rep, f ); + trap_FS_Write( "repIP = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->repIP, f ); + trap_FS_Write( "repGUID = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->repGUID, f ); + trap_FS_Write( "level = ", 10, f ); + admin_writeconfig_int( g_admin_reports[ i ]->level, f ); + trap_FS_Write( "note = ", 10, f ); + admin_writeconfig_string( g_admin_reports[ i ]->note, f ); + trap_FS_Write( "expires = ", 10, f ); + admin_writeconfig_int( g_admin_reports[ i ]->expires, f ); + trap_FS_Write( "closed = ", 10, f ); + admin_writeconfig_int( g_admin_reports[ i ]->closed, f ); + trap_FS_Write( "\n", 1, f ); + } + for( i = 0; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ]; i++ ) + { + if( ( g_admin_archives[ i ]->expires != 0 && ( g_admin_archives[ i ]->expires - t ) < 1 ) ) + { + continue; + } + + trap_FS_Write( "[archive]\n", 10, f ); + trap_FS_Write( "name = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->name, f ); + trap_FS_Write( "guid = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->guid, f ); + trap_FS_Write( "ip = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->ip, f ); + trap_FS_Write( "reason = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->reason, f ); + trap_FS_Write( "map = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->map, f ); + trap_FS_Write( "time = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->time, f ); + trap_FS_Write( "players = ", 10, f ); + admin_writeconfig_int( g_admin_archives[ i ]->players, f ); + trap_FS_Write( "admins = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->admins, f ); + trap_FS_Write( "rep = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->rep, f ); + trap_FS_Write( "repIP = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->repIP, f ); + trap_FS_Write( "repGUID = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->repGUID, f ); + trap_FS_Write( "level = ", 10, f ); + admin_writeconfig_int( g_admin_archives[ i ]->level, f ); + trap_FS_Write( "note = ", 10, f ); + admin_writeconfig_string( g_admin_archives[ i ]->note, f ); + trap_FS_Write( "expires = ", 10, f ); + admin_writeconfig_int( g_admin_archives[ i ]->expires, f ); + trap_FS_Write( "\n", 1, f ); + } + trap_FS_FCloseFile( f ); } static void admin_readconfig_string( char **cnf, char *s, int size ) @@ -888,7 +1020,7 @@ qboolean G_admin_chat_readconfig( gentity_t *ent ) if( !g_chat.string[ 0 ] ) { - ADMP( "chat_readconfig: g_chat is not set, not loading channel subscriptions " + ADMP( "^3chat_readconfig: ^7g_chat is not set, not loading channel subscriptions " "from a file\n" ); return qfalse; } @@ -896,7 +1028,7 @@ qboolean G_admin_chat_readconfig( gentity_t *ent ) len = trap_FS_FOpenFile( g_chat.string, &f, FS_READ ) ; if( len < 0 ) { - ADMP( va( "chat_readconfig: could not open chat config file %s\n", + ADMP( va( "^3chat_readconfig: ^7could not open chat config file %s\n", g_chat.string ) ); return qfalse; } @@ -956,7 +1088,7 @@ qboolean G_admin_chat_readconfig( gentity_t *ent ) } else { - ADMP( va( "chat_readconfig: [chat] parse error near %s on line %d\n", + ADMP( va( "^3chat_readconfig: ^7[chat] parse error near %s on line %d\n", t, COM_GetCurrentParseLine() ) ); } } @@ -965,7 +1097,7 @@ qboolean G_admin_chat_readconfig( gentity_t *ent ) } G_Free( cnf2 ); - ADMP( va( "chat_readconfig: loaded %d users with %d channels\n", uc, cc ) ); + ADMP( va( "^3chat_readconfig: ^7loaded %d users with %d channels\n", uc, cc ) ); return qtrue; } @@ -1842,42 +1974,46 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) g_admin_level_t * l = NULL; g_admin_admin_t *a = NULL; g_admin_ban_t *b = NULL; + g_admin_report_t *r = NULL; + g_admin_archive_t *s = NULL; g_admin_command_t *c = NULL; - int lc = 0, ac = 0, bc = 0, cc = 0; + int lc = 0, ac = 0, bc = 0, rc = 0, sc = 0, cc = 0; fileHandle_t f; int len; char *cnf, *cnf2; char *t; - qboolean level_open, admin_open, ban_open, command_open; + qboolean level_open, admin_open, ban_open, report_open, archive_open, command_open; int i; - + G_admin_cleanup(); if( !g_admin.string[ 0 ] ) { - ADMP( "^3!readconfig: g_admin is not set, not loading configuration " - "from a file\n" ); + ADMP( "^3!readconfig: g_admin is not set, using the built-in set of admin levels, " + "no ban entries, and no commands\n" ); admin_default_levels(); - return qfalse; + goto reportreadconfig; } - + len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ; if( len < 0 ) { ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n", g_admin.string ) ); admin_default_levels(); - return qfalse; + goto reportreadconfig; } + cnf = G_Alloc( len + 1 ); cnf2 = cnf; trap_FS_Read( cnf, len, f ); *( cnf + len ) = '\0'; trap_FS_FCloseFile( f ); - - t = COM_Parse( &cnf ); + + t = COM_Parse ( &cnf ); level_open = admin_open = ban_open = command_open = qfalse; - while( *t ) + + while( *t ) { if( !Q_stricmp( t, "[level]" ) || !Q_stricmp( t, "[admin]" ) || @@ -1892,9 +2028,8 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) else if( ban_open ) g_admin_bans[ bc++ ] = b; else if( command_open ) - g_admin_commands[ cc++ ] = c; - level_open = admin_open = - ban_open = command_open = qfalse; + g_admin_commands[ cc++ ] = c; + level_open = admin_open = ban_open = command_open = qfalse; } if( level_open ) @@ -2074,13 +2209,15 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) g_admin_admins[ ac++ ] = a; if( ban_open ) g_admin_bans[ bc++ ] = b; - if( command_open ) - g_admin_commands[ cc++ ] = c; - G_Free( cnf2 ); - ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n", - lc, ac, bc, cc ) ); + if( command_open ) + g_admin_commands[ cc++ ] = c; + G_Free( cnf2 ); + if( lc == 0 ) + { + ADMP( va( "^3!readconfig: ^7no admin levels found in %s, using the default set of admin levels\n", g_admin.string ) ); admin_default_levels(); + } else { char n[ MAX_NAME_LENGTH ] = {""}; @@ -2097,9 +2234,240 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) for( i = 0; i < level.maxclients; i++ ) if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] ); + + reportreadconfig: + + if( !g_report.string[ 0 ] ) + { + ADMP( "^3!readconfig: g_report is not set, using no reports and no archives\n" ); + ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands, %d reports, %d report archives\n", + lc, ac, bc, cc, rc, sc ) ); + return qtrue; + } + + len = trap_FS_FOpenFile( g_report.string, &f, FS_READ ) ; + if( len < 0 ) + { + ADMP( va( "^3!readconfig: ^7could not open report config file %s\n", + g_report.string ) ); + ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands, %d reports, %d report archives\n", + lc, ac, bc, cc, rc, sc ) ); + return qtrue; + } + + cnf = G_Alloc( len + 1 ); + cnf2 = cnf; + trap_FS_Read( cnf, len, f ); + *( cnf + len ) = '\0'; + trap_FS_FCloseFile( f ); - G_admin_chat_readconfig( ent ); + t = COM_Parse ( &cnf ); + report_open = archive_open = qfalse; + + while( *t ) + { + if( !Q_stricmp( t, "[report]" ) || + !Q_stricmp( t, "[archive]" ) ) + { + + if( report_open ) + g_admin_reports[ rc++ ] = r; + else if( archive_open ) + g_admin_archives[ sc++ ] = s; + report_open = archive_open = qfalse; + } + + if( report_open ) + { + if( !Q_stricmp( t, "name" ) ) + { + admin_readconfig_string( &cnf, r->name, sizeof( r->name ) ); + } + else if( !Q_stricmp( t, "guid" ) ) + { + admin_readconfig_string( &cnf, r->guid, sizeof( r->guid ) ); + } + else if( !Q_stricmp( t, "ip" ) ) + { + admin_readconfig_string( &cnf, r->ip, sizeof( r->ip ) ); + } + else if( !Q_stricmp( t, "reason" ) ) + { + admin_readconfig_string( &cnf, r->reason, sizeof( r->reason ) ); + } + else if( !Q_stricmp( t, "map" ) ) + { + admin_readconfig_string( &cnf, r->map, sizeof( r->map ) ); + } + else if( !Q_stricmp( t, "time" ) ) + { + admin_readconfig_string( &cnf, r->time, sizeof( r->time ) ); + } + else if( !Q_stricmp( t, "players" ) ) + { + admin_readconfig_int( &cnf, &r->players ); + } + else if( !Q_stricmp( t, "admins" ) ) + { + admin_readconfig_string( &cnf, r->admins, sizeof( r->admins ) ); + } + else if( !Q_stricmp( t, "rep" ) ) + { + admin_readconfig_string( &cnf, r->rep, sizeof( r->rep ) ); + } + else if( !Q_stricmp( t, "repIP" ) ) + { + admin_readconfig_string( &cnf, r->repIP, sizeof( r->repIP ) ); + } + else if( !Q_stricmp( t, "repGUID" ) ) + { + admin_readconfig_string( &cnf, r->repGUID, sizeof( r->repGUID ) ); + } + else if( !Q_stricmp( t, "level" ) ) + { + admin_readconfig_int( &cnf, &r->level ); + } + else if( !Q_stricmp( t, "note" ) ) + { + admin_readconfig_string( &cnf, r->note, sizeof( r->note ) ); + } + else if( !Q_stricmp( t, "expires" ) ) + { + admin_readconfig_int( &cnf, &r->expires ); + } + else if( !Q_stricmp( t, "closed" ) ) + { + admin_readconfig_int( &cnf, &r->closed ); + } + else + { + ADMP( va( "^3!readconfig: ^7[report] parse error near %s on line %d\n", + t, + COM_GetCurrentParseLine() ) ); + } + } + else if( archive_open ) + { + if( !Q_stricmp( t, "name" ) ) + { + admin_readconfig_string( &cnf, s->name, sizeof( s->name ) ); + } + else if( !Q_stricmp( t, "guid" ) ) + { + admin_readconfig_string( &cnf, s->guid, sizeof( s->guid ) ); + } + else if( !Q_stricmp( t, "ip" ) ) + { + admin_readconfig_string( &cnf, s->ip, sizeof( s->ip ) ); + } + else if( !Q_stricmp( t, "reason" ) ) + { + admin_readconfig_string( &cnf, s->reason, sizeof( s->reason ) ); + } + else if( !Q_stricmp( t, "map" ) ) + { + admin_readconfig_string( &cnf, s->map, sizeof( s->map ) ); + } + else if( !Q_stricmp( t, "time" ) ) + { + admin_readconfig_string( &cnf, s->time, sizeof( s->time ) ); + } + else if( !Q_stricmp( t, "players" ) ) + { + admin_readconfig_int( &cnf, &s->players ); + } + else if( !Q_stricmp( t, "admins" ) ) + { + admin_readconfig_string( &cnf, s->admins, sizeof( s->admins ) ); + } + else if( !Q_stricmp( t, "rep" ) ) + { + admin_readconfig_string( &cnf, s->rep, sizeof( s->rep ) ); + } + else if( !Q_stricmp( t, "repIP" ) ) + { + admin_readconfig_string( &cnf, s->repIP, sizeof( s->repIP ) ); + } + else if( !Q_stricmp( t, "repGUID" ) ) + { + admin_readconfig_string( &cnf, s->repGUID, sizeof( s->repGUID ) ); + } + else if( !Q_stricmp( t, "level" ) ) + { + admin_readconfig_int( &cnf, &s->level ); + } + else if( !Q_stricmp( t, "note" ) ) + { + admin_readconfig_string( &cnf, s->note, sizeof( s->note ) ); + } + else if( !Q_stricmp( t, "expires" ) ) + { + admin_readconfig_int( &cnf, &s->expires ); + } + else + { + ADMP( va( "^3!readconfig: ^7[archive] parse error near %s on line %d\n", + t, + COM_GetCurrentParseLine() ) ); + } + } + + if( !Q_stricmp( t, "[report]" ) ) + { + if( rc >= MAX_ADMIN_REPORTS ) + return qfalse; + r = G_Alloc( sizeof( g_admin_report_t ) ); + *r->name = '\0'; + *r->guid = '\0'; + *r->ip = '\0'; + *r->reason = '\0'; + *r->map = '\0'; + *r->time = '\0'; + r->players = 0; + *r->admins = '\0'; + *r->rep = '\0'; + *r->repIP = '\0'; + *r->repGUID = '\0'; + r->level = 0; + *r->note = '\0'; + r->expires = 0; + r->closed = 0; + report_open = qtrue; + } + else if( !Q_stricmp( t, "[archive]" ) ) + { + if( rc >= MAX_ADMIN_ARCHIVES ) + return qfalse; + s = G_Alloc( sizeof( g_admin_archive_t ) ); + *s->name = '\0'; + *s->guid = '\0'; + *s->ip = '\0'; + *s->reason = '\0'; + *s->map = '\0'; + *s->time = '\0'; + s->players = 0; + *s->admins = '\0'; + *s->rep = '\0'; + *s->repIP = '\0'; + *s->repGUID = '\0'; + s->level = 0; + *s->note = '\0'; + s->expires = 0; + archive_open = qtrue; + } + t = COM_Parse( &cnf ); + } + if( report_open ) + g_admin_reports[ rc++ ] = r; + if( archive_open ) + g_admin_archives[ sc++ ] = s; + G_Free( cnf2 ); + + ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d reports, %d report archives, %d commands\n", + lc, ac, bc, rc, sc, cc ) ); + G_admin_chat_readconfig( ent ); + return qtrue; } @@ -2113,6 +2481,20 @@ qboolean G_admin_time( gentity_t *ent, int skiparg ) return qtrue; } +static int admin_guid_to_level(const char *guid) +{ + int i; + + if (!strcmp(guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")) + return 0; + + for (i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[i]; i++) + if (!strcmp(guid, g_admin_admins[i]->guid)) + return g_admin_admins[i]->level; + + return 0; +} + static int G_admin_find_slot( gentity_t *ent, char *namearg, const char *command ) { char name[ MAX_NAME_LENGTH ]; @@ -5059,7 +5441,7 @@ qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) { // don't gather aka or level info if the admin is incognito - if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) ) + if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && !G_admin_permission(ent, ADMF_SEESINCOGNITO) ) { break; } @@ -6689,6 +7071,9 @@ static AdminFlagListEntry_t adminFlagList[] = { ADMF_TEAMCHANGEFREE, "keeps credits on team switch" }, { ADMF_TEAMCHAT_CMD, "can run commands from team chat" }, { ADMF_UNACCOUNTABLE, "does not need to specify reason for kick/ban" }, + { ADMF_NOREPORTLIMIT, "does not have a report limit" }, + { ADMF_FULLRLIST, "removes self-restriction on report list" }, + { ADMF_SEESINCOGNITO, "sees registered name of players flagged with INCOGNITO" }, { ADMF_NO_CHAT, "can not talk" }, { ADMF_NO_VOTE, "can not call votes" } }; @@ -8027,6 +8412,16 @@ void G_admin_cleanup() G_Free( g_admin_commands[ i ] ); g_admin_commands[ i ] = NULL; } + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + G_Free( g_admin_reports[ i ] ); + g_admin_reports[ i ] = NULL; + } + for( i = 0; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ]; i++ ) + { + G_Free( g_admin_archives[ i ] ); + g_admin_archives[ i ] = NULL; + } } qboolean G_admin_L0(gentity_t *ent, int skiparg ){ @@ -8234,3 +8629,1186 @@ qboolean G_admin_invisible( gentity_t *ent, int skiparg ) return qtrue; } +void G_admin_report_check( int clientNum ) +{ + char *guid; + char *ip; + int i; + gentity_t *ent; + qboolean hasGuid; + + ent = &g_entities[ clientNum ]; + + guid = ent->client->pers.guid; + ip = ent->client->pers.ip; + + hasGuid = !!Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ); + + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + if( g_admin_reports[ i ]->closed == 1 && + ( ( hasGuid && !Q_stricmp( g_admin_reports[ i ]->repGUID, guid ) ) || + ( !hasGuid && !Q_stricmp( g_admin_reports[ i ]->repIP, ip ) ) ) ) + { + ADMP( va("^5Your report for %s ^5was closed. Message from the admin:\n^7%s\n", + g_admin_reports[ i ]->name, g_admin_reports[ i ]->reason ) ); + g_admin_reports[ i ]->closed = 2; + + if( g_admin.string[ 0 ] ) + { + admin_writeconfig(); + } + + if( hasGuid ) + { + break; + } + } + } + return; +} + +static qboolean admin_create_archive( gentity_t *ent, + char *name, + char *guid, + char *ip, + char *reason, + char *map, + char *time, + int players, + char *admins, + char *rep, + char *repIP, + char *repGUID, + int level, + char *note, + int seconds ) + { + + g_admin_archive_t *s = NULL; + + qtime_t qt; + int t; + int i; + + for( i = 0; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ]; i++ ) + ; + if( i == MAX_ADMIN_ARCHIVES ) + { + ADMP( "^3!archive: ^7Too many archives\n" ); + return qfalse; + } + + t = trap_RealTime( &qt ); + s = G_Alloc( sizeof( g_admin_archive_t ) ); + + if( !s ) + return qfalse; + + Q_strncpyz( s->name, name, sizeof( s->name ) ); + Q_strncpyz( s->guid, guid, sizeof( s->guid ) ); + Q_strncpyz( s->ip, ip, sizeof( s->ip ) ); + Q_strncpyz( s->reason, reason, sizeof( s->reason ) ); + Q_strncpyz( s->map, map, sizeof( s->map ) ); + Q_strncpyz( s->time, time, sizeof( s->time ) ); + s->players = players; + Q_strncpyz( s->admins, admins, sizeof( s->admins ) ); + Q_strncpyz( s->rep, rep, sizeof( s->rep ) ); + Q_strncpyz( s->repIP, repIP, sizeof(s->repIP) ); + Q_strncpyz( s->repGUID, repGUID, sizeof(s->repGUID) ); + s->level = G_admin_level( ent ); + Q_strncpyz( s->note, note, sizeof(s->note) ); + if( !seconds ) { + s->expires = 0; + } else { + s->expires = t + seconds; + } + + g_admin_archives[ i ] = s; + return qtrue; +} + +static qboolean admin_create_report( gentity_t *ent, + char *name, + char *guid, + char *ip, + char *reason, + char *map, + char *time, + int players, + char *admins, + char *rep, + char *repIP, + char *repGUID, + int level, + char *note, + int seconds, + int closed ) + { + + g_admin_report_t *r = NULL; + + + qtime_t qt; + int t; + int i; + + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + ; + if( i == MAX_ADMIN_REPORTS ) + { + ADMP( "^3!report: ^7Too many reports\n" ); + return qfalse; + } + + t = trap_RealTime( &qt ); + r = G_Alloc( sizeof( g_admin_report_t ) ); + + if( !r ) + return qfalse; + + Q_strncpyz( r->name, name, sizeof( r->name ) ); + Q_strncpyz( r->guid, guid, sizeof( r->guid ) ); + Q_strncpyz( r->ip, ip, sizeof( r->ip ) ); + Q_strncpyz( r->reason, reason, sizeof( r->reason ) ); + Q_strncpyz( r->map, map, sizeof( r->map ) ); + Q_strncpyz( r->time, time, sizeof( r->time ) ); + r->players = players; + Q_strncpyz( r->admins, admins, sizeof( r->admins ) ); + Q_strncpyz( r->rep, rep, sizeof( r->rep ) ); + Q_strncpyz( r->repIP, repIP, sizeof(r->repIP) ); + Q_strncpyz( r->repGUID, repGUID, sizeof(r->repGUID) ); + r->level = G_admin_level( ent ); + Q_strncpyz( r->note, note, sizeof(r->note) ); + if( !seconds ) { + r->expires = 0; + } else { + r->expires = t + seconds; + } + r->closed = closed; + + g_admin_reports[ i ] = r; + return qtrue; +} + +qboolean G_admin_report( gentity_t *ent, int skiparg ) +{ + char *reason; + char search[ MAX_NAME_LENGTH ]; + char n2[ MAX_NAME_LENGTH ]; + char s2[ MAX_NAME_LENGTH ]; + char admins[256] = {0}; + char time[ 48 ]; + char map[ 32 ]; + int i, j, min, tens, sec; + int logmatch = -1, logmatches = 0, repcount = 0; + qboolean exactmatch = qfalse; + qtime_t qt; + gentity_t *vic; + + if( G_SayArgc() < 3 + skiparg ) + { + ADMP( "^3!report: ^7usage: !report [name|slot] [reason/additional info]\n" ); + return qfalse; + } + + trap_RealTime( &qt ); + + sec = ( level.time - level.startTime ) / 1000; + + min = sec / 60; + sec -= min * 60; + tens = sec / 10; + sec -= tens * 10; + + if( !G_admin_permission( ent, ADMF_NOREPORTLIMIT ) ) + { + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + if( !Q_stricmp( ent->client->pers.ip, g_admin_reports[ i ]->repIP ) || + !Q_stricmp( ent->client->pers.guid, g_admin_reports[ i ]->repGUID ) ) + { + repcount++; + } + + if( ( G_admin_level( ent ) == 0 && repcount >= g_maxUnregReports.integer ) + || repcount >= g_maxReports.integer ) + { + ADMP( "^5Your report limit was reached. You can view your reports using !rlist^7\n" ); + + if( repcount < g_maxReports.integer ) + { + ADMP( va( "^5^5Register to gain ^7%i ^5extra report slots.\n", (g_maxReports.integer - g_maxUnregReports.integer) ) ); + } + + return qfalse; + } + } + } + + G_SayArgv( 1 + skiparg, search, sizeof( search ) ); + G_SanitiseString( search, s2, sizeof( s2 ) ); + reason = G_SayConcatArgs( 2 + skiparg ); + + for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) + { + + if( g_admin_namelog[ i ]->slot == -1 ) + continue; + + if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) ) + { + logmatches = 1; + logmatch = i; + exactmatch = qtrue; + break; + } + } + + for( i = 0; + !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; + i++ ) + { + for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && + g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) + { + G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); + + if( strstr( n2, s2 ) ) + { + if( logmatch != i ) + logmatches++; + logmatch = i; + } + } + } + + if( !logmatches ) + { + ADMP( "^3!report: ^7no player found by that name or slot number\n" ); + return qfalse; + } + else if( logmatches > 1 ) + { + ADMBP_begin(); + ADMBP( "^3!report: ^7multiple recent clients match name, use slot#:\n" ); + + for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) + { + for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES && + g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) + { + G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); + + if( strstr( n2, s2 ) ) + { + if( g_admin_namelog[ i ]->slot > -1 ) + ADMBP( "^3" ); + ADMBP( va( "%-2s ^7- '%s^7'\n", + (g_admin_namelog[ i ]->slot > -1) ? + va( "%d", g_admin_namelog[ i ]->slot ) : "-", + g_admin_namelog[ i ]->name[ j ] ) ); + } + } + } + ADMBP_end(); + return qfalse; + } + + for( i = 0; i < level.maxclients; i++ ) + { + vic = &g_entities[ i ]; + + if( G_admin_level( vic ) >= 3 ) + { + Q_strcat( admins, sizeof(admins), va( "%s^7 ", G_admin_get_adminname( vic ) ) ); + } + } + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + Q_strncpyz( time, va( "^3%02i/%02i/%02i^7 - ^3%02i:%02i^7 - Game time: ^3%i:%i%i", qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, qt.tm_hour, qt.tm_min, min, tens, sec ), sizeof(time) ); + + admin_create_report( ent, + g_admin_namelog[ logmatch ]->name[0], + g_admin_namelog[ logmatch ]->guid, + g_admin_namelog[ logmatch ]->ip, + reason, map, time, level.numConnectedClients, admins, + ( ent ) ? ent->client->pers.netname : "console", ent->client->pers.ip, ent->client->pers.guid, G_admin_level( ent ), + "No notes - Edit this using !rnote", G_admin_parse_time("1w"), 0 ); + + if( !g_admin.string[ 0 ] ) + G_LogPrintf( "^3!report: ^7WARNING g_report not set, not saving report to a file\n" ); + else + admin_writeconfig(); + + G_AdminsPrintf( va( "New pending report from %s\n", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); + + ADMP( va( "^5You reported: ^7%s\n%s%s", g_admin_namelog[ logmatch ]->name[ 0 ], g_reportWelcomeComment.string, + ( g_reportWelcomeComment.string[ 0 ] ? "\n" : "" ) ) ); + + return qtrue; +} + +qboolean G_admin_rlist( gentity_t *ent, int skiparg ) +{ + char filter[ MAX_NAME_LENGTH ] = {""}; + char n1[ MAX_NAME_LENGTH * 2 ] = {""}; + char n2[ MAX_NAME_LENGTH * 2 ] = {""}; + char name_fmt[ 32 ] = { "%s" }; + char reporter_fmt[ 32 ] = { "%s" }; + char name_match[ MAX_NAME_LENGTH ] = {""}; + char duration[ 32 ]; + char *ip_match = NULL; + int i, t; + int show_count = 0; + int max_name = 1, max_reporter = 1; + int ip_match_len = 0; + int found = 0; + int secs; + int start = 0; + qboolean numeric = qtrue; + qboolean subnetfilter = qfalse; + qtime_t qt; + g_admin_report_t *report; + + t = trap_RealTime( &qt ); + + if( !G_admin_permission( ent, ADMF_FULLRLIST ) ) + { + ADMBP_begin(); + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + report = g_admin_reports[ i ]; + + if( !Q_stricmp( report->repGUID, ent->client->pers.guid ) && + report->closed == 0 ) + { + G_DecolorString( g_admin_reports[ i ]->name, n1 ); + Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", + ( max_name + (int)( strlen( g_admin_reports[ i ]->name ) - strlen( n1 ) ) ) ); + Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_reports[ i ]->name ); + + ADMBP( va( "%4i %s^7 Made: %s^7 on ^3%s^7 with ^3%i^7 players online\n \\__ ^5%s^7\n", + ( 1 + found ), + n1, + report->time, + report->map, + report->players, + report->reason ) ); + + found++; + } + } + + ADMBP_end(); + + if( found == 0 ) + { + ADMP( "^3!rlist: ^7You don't have any open reports pending.\n" ); + } + return qtrue; + } + + for( i = 0; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ]; i++ ) + { + if( g_admin_reports[ i ]->expires != 0 && + g_admin_reports[ i ]->expires - t < 1 && + g_admin_reports[ i ]->closed != 0 ) + { + continue; + } + found++; + } + + + if( G_SayArgc() >= 2 + skiparg ) + { + G_SayArgv( 1 + skiparg, filter, sizeof( filter ) ); + if( G_SayArgc() >= 3 + skiparg ) + { + start = atoi( filter ); + G_SayArgv( 2 + skiparg, filter, sizeof( filter ) ); + } + for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ ) + { + if( ( filter[ i ] < '0' || filter[ i ] > '9' ) && + filter[ i ] != '.' && filter[ i ] != '-' ) + { + numeric = qfalse; + break; + } + } + + if (!numeric) + { + if( filter[ 0 ] != '-' ) + { + G_SanitiseString( filter, name_match, sizeof( name_match) ); + + } + else + { + if( !Q_strncmp( filter, "-sub", 4 ) ) + { + subnetfilter = qtrue; + } else { + ADMP( va( "^3!rlist: ^7invalid argument %s\n", filter ) ); + return qfalse; + } + } + } + else if( strchr( filter, '.' ) != NULL ) + { + ip_match = filter; + ip_match_len = strlen(ip_match); + } + else + { + start = atoi( filter ); + filter[0] = '\0'; + } + + if( start > 0 ) + start -= 1; + else if( start < 0 ) + start = found + start; + } + + if( start >= MAX_ADMIN_REPORTS || start < 0 ) + start = 0; + + for( i = start; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ] && + show_count < MAX_ADMIN_SHOWREPORTS; i++ ) + { + qboolean match = qfalse; + report = g_admin_reports[ i ]; + + if (!numeric) + { + if( !subnetfilter ) + { + G_SanitiseString( report->name, n1, sizeof( n1 ) ); + if (strstr( n1, name_match) ) + match = qtrue; + } else { + int mask = -1; + int dummy; + int scanflen = 0; + scanflen = sscanf( report->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); + if( scanflen == 5 && mask < 32 ) + { + match = qtrue; + } + } + } + + if ( ( match ) || !ip_match || + Q_strncmp( ip_match, report->ip, ip_match_len) == 0 ) + { + G_DecolorString( report->name, n1 ); + G_DecolorString( report->rep, n2 ); + + if( strlen( n1 ) > max_name ) + { + max_name = strlen( n1 ); + } + if( strlen( n2 ) > max_reporter ) + max_reporter = strlen( n2 ); + + show_count++; + } + } + + if( start >= found ) + { + ADMP( va( "^3!rlist: ^7there are %d active reports\n", found ) ); + return qfalse; + } + + ADMBP_begin(); + show_count = 0; + + for( i = start; i < MAX_ADMIN_REPORTS && g_admin_reports[ i ] && + show_count < MAX_ADMIN_SHOWREPORTS; i++ ) + { + report = g_admin_reports[ i ]; + + if (!numeric) + { + if( !subnetfilter ) + { + G_SanitiseString( report->name, n1, sizeof( n1 ) ); + if ( strstr ( n1, name_match ) == NULL ) + continue; + } else { + int mask = -1; + int dummy; + int scanflen = 0; + scanflen = sscanf( report->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); + + if( scanflen != 5 || mask >= 32 ) + { + continue; + } + } + } + else if( ip_match != NULL + && Q_strncmp( ip_match, report->ip, ip_match_len ) != 0) + continue; + + if( ( report->expires == 0 || report->expires - t >= 1 ) && report->closed == 0 ) + { + G_DecolorString( report->name, n1 ); + Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", + ( max_name + (int)( strlen( report->name ) - strlen( n1 ) ) ) ); + Com_sprintf( n1, sizeof( n1 ), name_fmt, report->name ); + + G_DecolorString( report->rep, n2 ); + Com_sprintf( reporter_fmt, sizeof( reporter_fmt ), "%%%is", + ( max_reporter + (int)( strlen( report->rep ) - strlen( n2 ) ) ) ); + Com_sprintf( n2, sizeof( n2 ), reporter_fmt, report->rep ); + + secs = ( report->expires - t ); + G_admin_duration( secs, duration, sizeof( duration ) ); + + ADMBP( va( "%4i %s^7 %-15s Expires: %s\n | Reported by: %-15s^7 Level:%2i\n | ^5%s^7\n | Made: %s^7 on ^3%s^7 with ^3%i^7 players online\n | Admins online: %s^7\n \\__ ^5Note: %s^7\n", + ( i + 1 ), + n1, + report->ip, + duration, + n2, + report->level, + report->reason, + report->time, + report->map, + report->players, + report->admins, + report->note ) ); + + show_count++; + } + } + + if (!numeric || ip_match) + { + char matchmethod[50]; + + if( numeric ) + Com_sprintf( matchmethod, sizeof(matchmethod), "IP" ); + else if( !subnetfilter ) + Com_sprintf( matchmethod, sizeof(matchmethod), "name" ); + else + Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" ); + + ADMBP( va( "^3!rlist:^7 found %d matching reports by %s.", + show_count, + matchmethod ) ); + } else { + ADMBP( va( "^3!rlist:^7 showing reports %d - %d of %d.", + ( found ) ? ( start + 1 ) : 0, + ( ( start + MAX_ADMIN_SHOWREPORTS ) > found ) ? + found : ( start + MAX_ADMIN_SHOWREPORTS ), + found ) ); + } + + if( ( start + MAX_ADMIN_SHOWREPORTS ) < found ) + { + ADMBP( va( "run !rlist %d %s to see more", + ( start + MAX_ADMIN_SHOWREPORTS + 1 ), + (filter[0]) ? filter : "" ) ); + } + + ADMBP( "^3rlist:^7 run ^3!rshow^7 to see all details about a report" ); + + ADMBP( "\n" ); + ADMBP_end(); + return qtrue; +} + +qboolean G_admin_rban( gentity_t *ent, int skiparg ) +{ + int seconds; + char *reason; + int minargc; + char duration[ 32 ]; + char notice[51]; + char arg1[ 5 ]; + char arg2[ 7 ]; + int i, ID; + g_admin_report_t *report; + gclient_t *client; + + trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); + + if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && + G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) + { + minargc = 2 + skiparg; + } + else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) || + G_admin_permission( ent, ADMF_UNACCOUNTABLE ) || + g_adminMaxBan.integer ) + { + minargc = 3 + skiparg; + } + else + { + minargc = 4 + skiparg; + } + if( G_SayArgc() < minargc ) + { + ADMP( "^3!rban: ^7usage: !rban [report#] [time] [reason]\n" ); + return qfalse; + } + + G_SayArgv( 1 + skiparg, arg1, sizeof( arg1 ) ); + ID = atoi( arg1 ); + + if( ID < 1 || + ID > MAX_ADMIN_REPORTS || + !g_admin_reports[ ID - 1 ] || + g_admin_reports[ ID - 1 ]->closed == 2 ) + { + ADMP( "^3!rban: ^7invalid report#\n" ); + return qfalse; + } + + report = g_admin_reports[ ID - 1 ]; + + if( admin_guid_to_level( ent->client->pers.guid ) < + admin_guid_to_level( g_admin_reports[ ID ]->guid ) ) + { + ADMP( "Your target has a higher admin level than you." ); + return qfalse; + } + + G_SayArgv( 2 + skiparg, arg2, sizeof( arg2 ) ); + seconds = G_admin_parse_time( arg2 ); + + if( seconds <= 0 ) + { + if( g_adminMaxBan.integer && !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) + { + ADMP( va( "^3!rban: ^7using your admin level's maximum ban length of %s\n", + g_adminMaxBan.string ) ); + seconds = G_admin_parse_time( g_adminMaxBan.string ); + } + else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) + { + seconds = 0; + } + else + { + ADMP( "^3!rban: ^7ban time must be positive\n" ); + return qfalse; + } + reason = G_SayConcatArgs( 2 + skiparg ); + } + else + { + reason = G_SayConcatArgs( 3 + skiparg ); + + if( g_adminMaxBan.integer && + seconds > G_admin_parse_time( g_adminMaxBan.string ) && + !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) + { + seconds = G_admin_parse_time( g_adminMaxBan.string ); + ADMP( va( "^3!rban: ^7ban length limited to %s for your admin level\n", + g_adminMaxBan.string ) ); + } + } + + G_admin_duration( ( seconds ) ? seconds : -1, + duration, sizeof( duration ) ); + + admin_create_ban( ent, + report->name, + report->guid, + report->ip, + seconds, reason ); + + if( !g_admin.string[ 0 ] ) + ADMP( "^3!rban: ^7WARNING g_admin not set, not saving ban to a file\n" ); + else + admin_writeconfig(); + + AP( va( "print \"^3!rban:^7 %s^7 has been banned by %s^7 " + "duration: %s, reason: %s\n\"", + report->name, + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + duration, + ( *reason ) ? reason : "banned by admin" ) ); + + for( i = 0; i < level.maxclients; i++ ) + { + client = &level.clients[ i ]; + + if( client->pers.connected == CON_DISCONNECTED ) + { + continue; + } + + if( !Q_stricmp( client->pers.ip, report->ip ) || + ( Q_stricmp( report->guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) && + !Q_stricmp ( client->pers.guid, report->guid ) ) ) + + trap_DropClient( i, + va( "banned by %s^7, duration: %s, reason: %s", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + duration, + ( *reason ) ? reason : "banned by admin" ) ); + } + + return qtrue; +} + +qboolean G_admin_rclose( gentity_t *ent, int skiparg ) +{ + char *reason; + char arg1[ 5 ]; + char arg2[ 2 ]; + int i, ID; + gclient_t *client; + g_admin_report_t *report; + + if( G_SayArgc() < 3 + skiparg ) + { + ADMP( "^3!rclose: ^7usage: !rclose [report#] (!) [message] - ! saves it as archive\n" ); + return qfalse; + } + + G_SayArgv( 1 + skiparg, arg1, sizeof( arg1 ) ); + ID = atoi( arg1 ); + + if( ID < 1 || ID > MAX_ADMIN_REPORTS || !g_admin_reports[ ID - 1 ] || g_admin_reports[ ID - 1 ]->closed >= 1 ) + { + ADMP( "^3!rclose: ^7invalid report#\n" ); + return qfalse; + } + + report = g_admin_reports[ ID - 1 ]; + + G_SayArgv( 2 + skiparg, arg2, sizeof( arg2 ) ); + reason = G_SayConcatArgs( 3 + skiparg ); + + if( !strcmp( arg2, "!" ) ) + { + admin_create_archive( ent, + report->name, + report->guid, + report->ip, + report->reason, + report->map, + report->time, + report->players, + report->admins, + report->rep, + report->repIP, + report->repGUID, + report->level, + report->note, + G_admin_parse_time("12w") ); + + G_AdminsPrintf( va( "%s ^7closed and archived report #%i\n", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", ID ) ); + } else { + reason = G_SayConcatArgs( 2 + skiparg ); + G_AdminsPrintf( va( "%s ^7closed report #%i\n", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + ID ) ); + } + + report->closed = 1; + Q_strncpyz( report->reason, reason, sizeof( report->reason ) ); + + for( i = 0; i < level.maxclients ; i++ ) + { + client = &level.clients[ i ]; + + if( client->pers.connected == CON_DISCONNECTED ) + { + continue; + } + + if( !Q_stricmp( report->repGUID, client->pers.guid ) || + !strcmp( report->repIP, client->pers.ip ) ) + { + trap_SendServerCommand( i, va( "print \"^5Your report for %s ^5was closed. Message from the admin:\n^7%s\n\"", + report->name, report->reason ) ); + report->closed = 2; + break; + } + } + + if( !g_admin.string[ 0 ] ) + ADMP( "^3!rclose: ^7WARNING g_admin not set, not saving report to a file\n" ); + else + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_rarclist( gentity_t *ent, int skiparg ) +{ + char filter[ MAX_NAME_LENGTH ] = {""}; + char n1[ MAX_NAME_LENGTH * 2 ] = {""}; + char n2[ MAX_NAME_LENGTH * 2 ] = {""}; + char name_fmt[ 32 ] = { "%s" }; + char reporter_fmt[ 32 ] = { "%s" }; + char name_match[ MAX_NAME_LENGTH ] = {""}; + char duration[ 32 ]; + char *ip_match = NULL; + int show_count = 0; + int max_name = 1, max_reporter = 1; + int ip_match_len = 0; + int secs; + int found = 0; + int start = 0; + int i, t; + qboolean numeric = qtrue; + qboolean subnetfilter = qfalse; + g_admin_archive_t *archive; + qtime_t qt; + + t = trap_RealTime( &qt ); + + for( i = 0; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ]; i++ ) + { + archive = g_admin_archives[ i ]; + + if( archive->expires != 0 && + archive->expires - t < 1 ) + { + continue; + } + found++; + } + + + if( G_SayArgc() >= 2 + skiparg ) + { + G_SayArgv( 1 + skiparg, filter, sizeof( filter ) ); + if( G_SayArgc() >= 3 + skiparg ) + { + start = atoi( filter ); + G_SayArgv( 2 + skiparg, filter, sizeof( filter ) ); + } + for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ ) + { + if( ( filter[ i ] < '0' || filter[ i ] > '9' ) && + filter[ i ] != '.' && filter[ i ] != '-' ) + { + numeric = qfalse; + break; + } + } + + if (!numeric) + { + if( filter[ 0 ] != '-' ) + { + G_SanitiseString( filter, name_match, sizeof( name_match) ); + + } else { + if( !Q_strncmp( filter, "-sub", 4 ) ) + { + subnetfilter = qtrue; + } else { + ADMP( va( "^3!rarclist: ^7invalid argument %s\n", filter ) ); + return qfalse; + } + } + } + else if( strchr( filter, '.' ) != NULL ) + { + ip_match = filter; + ip_match_len = strlen(ip_match); + } else { + start = atoi( filter ); + filter[0] = '\0'; + } + + if( start > 0 ) + start -= 1; + else if( start < 0 ) + start = found + start; + } + + if( start >= MAX_ADMIN_ARCHIVES || start < 0 ) + start = 0; + + for( i = start; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ] + && show_count < MAX_ADMIN_SHOWREPORTS; i++ ) + { + qboolean match = qfalse; + archive = g_admin_archives[ i ]; + + if (!numeric) + { + if( !subnetfilter ) + { + G_SanitiseString( archive->name, n1, sizeof( n1 ) ); + if (strstr( n1, name_match) ) + match = qtrue; + } + else + { + int mask = -1; + int dummy; + int scanflen = 0; + scanflen = sscanf( archive->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); + if( scanflen == 5 && mask < 32 ) + { + match = qtrue; + } + } + } + + if ( ( match ) || !ip_match || + Q_strncmp( ip_match, archive->ip, ip_match_len) == 0 ) + { + G_DecolorString( archive->name, n1 ); + G_DecolorString( archive->rep, n2 ); + + if( strlen( n1 ) > max_name ) + { + max_name = strlen( n1 ); + } + if( strlen( n2 ) > max_reporter ) + max_reporter = strlen( n2 ); + + show_count++; + } + } + + if( start >= found ) + { + ADMP( va( "^3!rarclist: ^7there are %d archived reports\n", found ) ); + return qfalse; + } + + ADMBP_begin(); + show_count = 0; + + for( i = start; i < MAX_ADMIN_ARCHIVES && g_admin_archives[ i ] && + show_count < MAX_ADMIN_SHOWREPORTS; i++ ) + { + archive = g_admin_archives[ i ]; + if (!numeric) + { + if( !subnetfilter ) + { + G_SanitiseString( archive->name, n1, sizeof( n1 ) ); + if ( strstr ( n1, name_match ) == NULL ) + continue; + } + else + { + int mask = -1; + int dummy; + int scanflen = 0; + scanflen = sscanf( archive->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); + if( scanflen != 5 || mask >= 32 ) + { + continue; + } + } + } + else if( ip_match != NULL && + Q_strncmp( ip_match, archive->ip, ip_match_len ) != 0) + continue; + + if( archive->expires == 0 || archive->expires - t >= 1 ) + { + G_DecolorString( archive->name, n1 ); + Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", + ( max_name + (int)( strlen( archive->name ) - strlen( n1 ) ) ) ); + Com_sprintf( n1, sizeof( n1 ), name_fmt, archive->name ); + + G_DecolorString( archive->rep, n2 ); + Com_sprintf( reporter_fmt, sizeof( reporter_fmt ), "%%%is", + ( max_reporter + (int)( strlen( archive->rep ) - strlen( n2 ) ) ) ); + Com_sprintf( n2, sizeof( n2 ), reporter_fmt, archive->rep ); + + secs = ( archive->expires - t ); + G_admin_duration( secs, duration, sizeof( duration ) ); + + ADMBP( va( "%4i %s^7 %-15s Archive expires in: %s\n | Reported by: %-15s^7 Level:%2i\n | ^5%s^7\n | Made: %s^7 on ^3%s^7 with ^3%i^7 players online\n | Admins online: %s\n \\__ Note: ^5%s^7\n", + ( i + 1 ), + n1, + archive->ip, + duration, + n2, + archive->level, + archive->reason, + archive->time, + archive->map, + archive->players, + archive->admins, + archive->note ) ); + } + show_count++; + } + + if (!numeric || ip_match) + { + char matchmethod[50]; + if( numeric ) + Com_sprintf( matchmethod, sizeof(matchmethod), "IP" ); + else if( !subnetfilter ) + Com_sprintf( matchmethod, sizeof(matchmethod), "name" ); + else + Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" ); + + + ADMBP( va( "^3!rarclist:^7 found %d matching archived reports by %s. ", + show_count, + matchmethod ) ); + } + else + { + ADMBP( va( "^3!rarclist:^7 showing archived reports %d - %d of %d. ", + ( found ) ? ( start + 1 ) : 0, + ( ( start + MAX_ADMIN_SHOWREPORTS ) > found ) ? + found : ( start + MAX_ADMIN_SHOWREPORTS ), + found ) ); + } + + if( ( start + MAX_ADMIN_SHOWREPORTS ) < found ) + { + ADMBP( va( "run !rarclist %d %s to see more", + ( start + MAX_ADMIN_SHOWREPORTS + 1 ), + (filter[0]) ? filter : "" ) ); + } + + ADMBP( "^3rlist:^7 run ^3!rshow^7 ^5!^7 to see all details about an archive" ); + + ADMBP( "\n" ); + ADMBP_end(); + return qtrue; +} + +qboolean G_admin_rpurge( gentity_t *ent, int skiparg ) +{ + char arg2[ 5 ]; + char tmp[ 2 ]; + int ID, t; + + t = trap_RealTime( NULL ); + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!rpurge: ^7usage: !rpurge [reportid#] (!) - if '!' is specified, acts on archive# instead\n" ); + return qfalse; + } + + G_SayArgv( 1 + skiparg, arg2, sizeof( arg2 ) ); + ID = atoi( arg2 ); + + if( G_SayArgc() >= 3 + skiparg ) + { + G_SayArgv( 2 + skiparg, tmp, sizeof( tmp ) ); + + if( !strcmp( tmp, "!") ) + { + if( ID < 1 || + ID > MAX_ADMIN_ARCHIVES || + !g_admin_archives[ ID - 1 ] || + ( g_admin_archives[ ID - 1 ]->expires != 0 && g_admin_archives[ ID - 1 ]->expires - t < 1 ) ) + { + ADMP( "^3!rpurge: ^7invalid report archive#\n" ); + return qfalse; + } + + G_AdminsPrintf( va( "%s ^7purged archived report #%i\n", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", ID ) ); + g_admin_archives[ ID - 1 ]->expires = t; + + if( g_report.string[ 0 ] ) + admin_writeconfig(); + + return qtrue; + } else { + ADMP( "^1WROOOOOOOOONG!^7: ^3!rpurge: ^7usage: !rpurge [reportid#] (!) - if '!' is specified, acts on archive# instead\n" ); + return qfalse; + } + } + + if( ID < 1 || + ID > MAX_ADMIN_REPORTS || + !g_admin_reports[ ID - 1 ] || + ( g_admin_reports[ ID - 1 ]->expires != 0 && g_admin_reports[ ID - 1 ]->expires - t < 1 ) || + g_admin_reports[ ID - 1 ]->closed != 0 ) + { + ADMP( "^3!rpurge: ^7invalid report#\n" ); + return qfalse; + } + + G_AdminsPrintf( va( "%s ^7purged report #%i\n", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + ID ) ); + g_admin_reports[ ID - 1 ]->closed = 2; + + if( g_report.string[ 0 ] ) + admin_writeconfig(); + + return qtrue; +} + +qboolean G_admin_rnote( gentity_t *ent, int skiparg ) +{ + char *note; + char tmp[ 2 ]; + char arg2[ 5 ]; + int ID; + + if( G_SayArgc() < 3 + skiparg ) + { + ADMP( "^3!rnote: ^7usage: !rnote [report#] (!) [note] - adding ! means rnote archive# instead of report#\n" ); + return qfalse; + } + + G_SayArgv( 1 + skiparg, arg2, sizeof( arg2 ) ); + ID = atoi( arg2 ); + G_SayArgv( 2 + skiparg, tmp, sizeof( tmp ) ); + note = G_SayConcatArgs( 3 + skiparg ); + + if( !strcmp( tmp, "!" ) ) + { + if( ID < 1 || + ID > MAX_ADMIN_ARCHIVES || + !g_admin_archives[ ID - 1] ) + { + ADMP( "^3!rnote: ^7invalid archive#\n" ); + return qfalse; + } + + Q_strncpyz(g_admin_archives[ ID - 1 ]->note, + note, sizeof( g_admin_archives[ ID - 1 ]->note ) ); + ADMP( va( "^5Note added to archive #%i\n", ID ) ); + return qtrue; + } else { + note = G_SayConcatArgs( 2 + skiparg ); + } + + if( ID < 1 || + ID > MAX_ADMIN_REPORTS || + !g_admin_reports[ ID - 1] ) + { + ADMP( "^3!rnote: ^7invalid report#\n" ); + return qfalse; + } + + + ADMP( va( "^5Note added to report #%i\n", ID ) ); + Q_strncpyz(g_admin_reports[ ID - 1 ]->note, + note, sizeof( g_admin_reports[ ID - 1 ]->note ) ); + + if( !g_admin.string[ 0 ] ) + ADMP( "^3!rclose: ^7WARNING g_admin not set, not saving ban to a file\n" ); + else + admin_writeconfig(); + + return qtrue; +} + diff --git a/src/game/g_admin.h b/src/game/g_admin.h index 6e6e57a..d46b5fa 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -44,6 +44,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define MAX_ADMIN_COMMANDS 64 #define MAX_ADMIN_CMD_LEN 20 #define MAX_ADMIN_BAN_REASON 50 +#define MAX_ADMIN_REPORTS 1024 +#define MAX_ADMIN_REPORTS_REASON 256 +#define MAX_ADMIN_ARCHIVES 1024 +#define MAX_ADMIN_ARCHIVES_REASON 256 #define MAX_ADMIN_BANSUSPEND_DAYS 14 @@ -93,12 +97,18 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define ADMF_BAN_IMMUNITY "BANIMMUNITY" +#define ADMF_FULLRLIST "FULLRLIST" +#define ADMF_NOREPORTLIMIT "NOREPORTLIMIT" +#define ADMF_SEESINCOGNITO "SEESINCOGNITO" + #define ADMF_NO_CHAT ".NOCHAT" #define ADMF_NO_VOTE ".NOVOTE" #define MAX_ADMIN_LISTITEMS 20 #define MAX_ADMIN_SHOWBANS 10 +#define MAX_ADMIN_SHOWREPORTS 5 + #define MAX_ADMIN_MAPLOG_LENGTH 5 // important note: QVM does not seem to allow a single char to be a @@ -197,6 +207,46 @@ typedef struct g_admin_tklog } g_admin_tklog_t; +typedef struct g_admin_report +{ + char name[ 128 ]; + char guid[ 33 ]; + char ip[ 20 ]; + char reason[ MAX_ADMIN_REPORTS_REASON ]; + char map[ 50 ]; + char time[ 48 ]; + int players; + char admins[ 256 ]; + char rep[ 70 ]; + char repIP[ 20 ]; + char repGUID[ 33 ]; + int level; + char note[ MAX_ADMIN_REPORTS_REASON ]; + int expires; + int closed; +} +g_admin_report_t; + +typedef struct g_admin_archive +{ + char name[ 128 ]; + char guid[ 33 ]; + char ip[ 20 ]; + char reason[ MAX_ADMIN_REPORTS_REASON ]; + char map[ 50 ]; + char time[ 48 ]; + int players; + char admins[ 256 ]; + char rep[ 70 ]; + char repIP[ 20 ]; + char repGUID[ 33 ]; + int level; + char note[ MAX_ADMIN_REPORTS_REASON ]; + int expires; +} +g_admin_archive_t; + + qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ); qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ); qboolean G_admin_readconfig( gentity_t *ent, int skiparg ); @@ -285,6 +335,14 @@ qboolean G_admin_slap( gentity_t *ent, int skiparg ); qboolean G_admin_drop( gentity_t *ent, int skiparg ); qboolean G_admin_bubble( gentity_t *ent, int skiparg ); +qboolean G_admin_report( gentity_t *ent, int skiparg ); +qboolean G_admin_rlist( gentity_t *ent, int skiparg ); +qboolean G_admin_rban( gentity_t *ent, int skiparg ); +qboolean G_admin_rarclist( gentity_t *ent, int skiparg ); +qboolean G_admin_rclose( gentity_t *ent, int skiparg ); +qboolean G_admin_rpurge( gentity_t *ent, int skiparg ); +qboolean G_admin_rnote( gentity_t *ent, int skiparg ); + void G_admin_print( gentity_t *ent, char *m ); void G_admin_buffer_print( gentity_t *ent, char *m ); void G_admin_buffer_begin( void ); @@ -294,4 +352,6 @@ void G_admin_duration( int secs, char *duration, int dursize ); void G_admin_cleanup( void ); void G_admin_namelog_cleanup( void ); +void G_admin_report_check( int clientNum ); + #endif /* ifndef _G_ADMIN_H */ diff --git a/src/game/g_client.c b/src/game/g_client.c index 6d8acfe..985c03d 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1618,6 +1618,9 @@ void ClientBegin( int clientNum ) // rejoin any saved chat channels G_admin_chat_sync( ent ); + // report confirmation + G_admin_report_check( clientNum ); + // request the clients PTR code trap_SendServerCommand( ent - g_entities, "ptrcrequest" ); } diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index dad9cfe..723a2a5 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1140,15 +1140,15 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) case SAY_ADMINS: if( G_admin_permission( ent, ADMF_ADMINCHAT ) ) //Differentiate between inter-admin chatter and user-admin alerts { - G_LogPrintf( "say_admins: [ADMIN]%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); - Com_sprintf( name, sizeof( name ), "%s[ADMIN]%s%c%c"EC": ", prefix, + G_LogPrintf( "say_admins: ^7[^6ADMIN^7]%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); + Com_sprintf( name, sizeof( name ), "%s^7[^6ADMIN^7]%s%c%c"EC": ", prefix, ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_MAGENTA; } else { - G_LogPrintf( "say_admins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText ); - Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix, + G_LogPrintf( "say_admins: ^7[^3PLAYER^7]%s^7: %s^7\n", ent->client->pers.netname, chatText ); + Com_sprintf( name, sizeof( name ), "%s^7[^3PLAYER^7]%s%c%c"EC": ", prefix, ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_MAGENTA; } @@ -2812,7 +2812,7 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) || level.clients[ i ].pers.teamSelection == PTE_NONE ) ) { - trap_SendServerCommand( i, va("print \"^6[Admins]^7 %s " S_COLOR_WHITE + trap_SendServerCommand( i, va("print \"^7[^5ADMIN^7] %s " S_COLOR_WHITE "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); } } @@ -5949,7 +5949,7 @@ void G_CP( gentity_t *ent ) { if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) { - trap_SendServerCommand( i, va("print \"^6[Admins]^7 CP to other team%s: %s \n\"", prefixes, text ) ); + trap_SendServerCommand( i, va("print \"^7[^5ADMIN^7] CP to other team%s: %s \n\"", prefixes, text ) ); } continue; } diff --git a/src/game/g_local.h b/src/game/g_local.h index d51cf1b..09cce52 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -1448,6 +1448,7 @@ extern vmCvar_t g_layouts; extern vmCvar_t g_layoutAuto; extern vmCvar_t g_admin; +extern vmCvar_t g_report; extern vmCvar_t g_adminLog; extern vmCvar_t g_adminParseSay; extern vmCvar_t g_adminSayFilter; @@ -1533,6 +1534,11 @@ extern vmCvar_t mod_jetpackFuel; extern vmCvar_t mod_jetpackConsume; extern vmCvar_t mod_jetpackRegen; +extern vmCvar_t g_maxReports; +extern vmCvar_t g_maxUnregReports; + +extern vmCvar_t g_reportWelcomeComment; + void trap_Printf( const char *fmt ); void trap_Error( const char *fmt ); int trap_Milliseconds( void ); diff --git a/src/game/g_main.c b/src/game/g_main.c index 105a95e..b535e73 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -183,6 +183,7 @@ vmCvar_t g_layouts; vmCvar_t g_layoutAuto; vmCvar_t g_admin; +vmCvar_t g_report; vmCvar_t g_adminLog; vmCvar_t g_adminParseSay; vmCvar_t g_adminSayFilter; @@ -274,6 +275,11 @@ vmCvar_t mod_jetpackFuel; vmCvar_t mod_jetpackConsume; vmCvar_t mod_jetpackRegen; +vmCvar_t g_maxReports; +vmCvar_t g_maxUnregReports; + +vmCvar_t g_reportWelcomeComment; + static cvarTable_t gameCvarTable[ ] = { // don't override the cheat state set by the system @@ -452,6 +458,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, + { &g_report, "g_report", "report.dat", CVAR_ARCHIVE, 0, qfalse }, { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse }, { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse }, @@ -525,8 +532,12 @@ static cvarTable_t gameCvarTable[ ] = { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qfalse }, { &mod_jetpackConsume, "mod_jetpackConsume", "2", CVAR_ARCHIVE, 0, qfalse }, - { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse } + { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse }, + + { &g_maxReports, "g_maxReports", "3", CVAR_ARCHIVE, 0, qfalse }, + { &g_maxUnregReports, "g_maxUnregReports", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_reportWelcomeComment, "g_reportWelcomeComment", "", CVAR_ARCHIVE, 0, qfalse } }; static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); @@ -2141,7 +2152,7 @@ void QDECL G_AdminsPrintf( const char *fmt, ... ) if( G_admin_permission( tempent, ADMF_ADMINCHAT) && !tempent->client->pers.ignoreAdminWarnings ) { - trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) ); + trap_SendServerCommand(tempent-g_entities,va( "print \"^7[^5ADMIN^7] %s\"", string) ); } } -- cgit