summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game/g_admin.c260
-rw-r--r--src/game/g_admin.h4
-rw-r--r--src/game/g_client.c8
-rw-r--r--src/game/g_local.h14
-rw-r--r--src/game/g_utils.c189
5 files changed, 361 insertions, 114 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c
index d2125dd7..bcd2b8e7 100644
--- a/src/game/g_admin.c
+++ b/src/game/g_admin.c
@@ -44,7 +44,7 @@ g_admin_cmd_t g_admin_cmds[ ] =
"'m' (minutes), or seconds if no units are specified. if the duration is"
" preceded by a + or -, the ban duration will be extended or shortened by"
" the specified amount",
- "[^3ban#^7] (^5duration^7) (^5reason^7)"
+ "[^3ban#^7] (^5/mask^7) (^5duration^7) (^5reason^7)"
},
{"admintest", G_admin_admintest, "admintest",
@@ -67,7 +67,7 @@ g_admin_cmd_t g_admin_cmds[ ] =
" duration is specified as numbers followed by units 'w' (weeks), 'd' "
"(days), 'h' (hours) or 'm' (minutes), or seconds if no units are "
"specified",
- "[^3name|slot#|IP^7] (^5duration^7) (^5reason^7)"
+ "[^3name|slot#|IP(/mask)^7] (^5duration^7) (^5reason^7)"
},
{"cancelvote", G_admin_endvote, "cancelvote",
@@ -122,7 +122,7 @@ g_admin_cmd_t g_admin_cmds[ ] =
{"namelog", G_admin_namelog, "namelog",
"display a list of names used by recently connected players",
- "(^5name^7)"
+ "(^5name|IP(/mask)^7)"
},
{"nextmap", G_admin_nextmap, "nextmap",
@@ -162,7 +162,7 @@ g_admin_cmd_t g_admin_cmds[ ] =
{"showbans", G_admin_showbans, "showbans",
"display a (partial) list of active bans",
- "(^5start at ban#^7) (^5name|IP^7)"
+ "(^5start at ban#^7) (^5name|IP(/mask)^7)"
},
{"spec999", G_admin_spec999, "spec999",
@@ -803,40 +803,42 @@ void G_admin_duration( int secs, char *duration, int dursize )
Com_sprintf( duration, dursize, "%i seconds", secs );
}
-qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen )
+qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen )
{
- char *guid, *ip;
int i;
int t;
+ addr_t ip, test;
+ int mask = -1;
*reason = '\0';
t = trap_RealTime( NULL );
- if( !*userinfo )
+ // this happens when ip = localhost
+ if( !G_AddressParse( ent->client->pers.ip, &ip, NULL ) )
return qfalse;
- ip = Info_ValueForKey( userinfo, "ip" );
- if( !*ip )
- return qfalse;
- guid = Info_ValueForKey( userinfo, "cl_guid" );
for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ )
{
// 0 is for perm ban
if( g_admin_bans[ i ]->expires != 0 && g_admin_bans[ i ]->expires <= t )
continue;
- if( strstr( ip, g_admin_bans[ i ]->ip ) ||
- ( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) ) )
+
+ if( !Q_stricmp( g_admin_bans[ i ]->guid, ent->client->pers.guid ) ||
+ ( !G_admin_permission( ent, ADMF_IMMUNITY ) &&
+ G_AddressParse( g_admin_bans[ i ]->ip, &test, &mask ) &&
+ G_AddressCompare( &ip, &test, mask ) ) )
{
char duration[ 13 ];
G_admin_duration( g_admin_bans[ i ]->expires - t,
duration, sizeof( duration ) );
- Com_sprintf(
- reason,
- rlen,
- "You have been banned by %s^7 reason: %s^7 expires: %s",
- g_admin_bans[ i ]->banner,
- g_admin_bans[ i ]->reason,
- duration
- );
- G_Printf( "Banned player (#%d) tried to connect from %s\n", i + 1, ip );
+ if( reason )
+ Com_sprintf(
+ reason,
+ rlen,
+ "You have been banned by %s^7 reason: %s^7 expires: %s",
+ g_admin_bans[ i ]->banner,
+ g_admin_bans[ i ]->reason,
+ duration
+ );
+ G_Printf( "%s matches ban #%d\n", ip, i + 1 );
return qtrue;
}
}
@@ -1433,6 +1435,21 @@ static qboolean admin_create_ban( gentity_t *ent,
Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) );
else
Q_strncpyz( b->reason, reason, sizeof( b->reason ) );
+
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
+ continue;
+ if( G_admin_ban_check( &g_entities[ i ], NULL, 0 ) )
+ {
+ trap_SendServerCommand( i,
+ va( "disconnect \"You have been kicked by %s\nreason:\n%s\"",
+ b->banner, b->reason ) );
+
+ trap_DropClient( i, va( "has been kicked by %s^7. reason: %s",
+ b->banner, b->reason ) );
+ }
+ }
return qtrue;
}
@@ -1509,15 +1526,6 @@ qboolean G_admin_kick( gentity_t *ent, int skiparg )
( *reason ) ? reason : "kicked by admin" );
admin_writeconfig();
- trap_SendServerCommand( pids[ 0 ],
- va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\"",
- ( ent ) ? va( "admin:\n%s", ent->client->pers.netname ) : "",
- ( *reason ) ? reason : "kicked by admin" ) );
-
- trap_DropClient( pids[ 0 ], va( "has been kicked%s^7. reason: %s",
- ( ent ) ? va( " by %s", ent->client->pers.netname ) : "",
- ( *reason ) ? reason : "kicked by admin" ) );
-
return qtrue;
}
@@ -1533,10 +1541,13 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
qboolean exactmatch = qfalse;
char n2[ MAX_NAME_LENGTH ];
char s2[ MAX_NAME_LENGTH ];
+ int netmask = -1;
+ addr_t ip, cmp;
+ qboolean ipmatch = qfalse;
if( G_SayArgc() < 2 + skiparg )
{
- ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [duration] [reason]\n" );
+ ADMP( "^3!ban: ^7usage: !ban [name|slot|IP(/mask)] [duration] [reason]\n" );
return qfalse;
}
G_SayArgv( 1 + skiparg, search, sizeof( search ) );
@@ -1581,6 +1592,18 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
for( logmatch = 0; g_admin_namelog[ logmatch ]->slot != i; logmatch++ );
}
}
+ else if( G_AddressParse( search, &ip, &netmask ) )
+ {
+ int max = ip.type == IPv4 ? 32 : 128;
+ int min = ent ? max / 2 : 1;
+ if( netmask < min || netmask > max )
+ {
+ ADMP( va( "^3!ban: ^7invalid netmask (%d is not one of %d-%d)\n",
+ netmask, min, max ) );
+ return qfalse;
+ }
+ ipmatch = qtrue;
+ }
for( i = 0;
!exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ];
@@ -1590,12 +1613,21 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
if( g_admin_namelog[ i ]->banned )
continue;
- if( !Q_stricmp( g_admin_namelog[ i ]->ip, search ) )
+ if( ipmatch )
{
- logmatches = 1;
- logmatch = i;
- exactmatch = qtrue;
- break;
+ if( G_AddressParse( g_admin_namelog[ i ]->ip, &cmp, NULL ) &&
+ G_AddressCompare( &ip, &cmp, netmask ) )
+ {
+ logmatches++;
+ logmatch = i;
+ if( ( ip.type == IPv4 && netmask == 32 ) ||
+ ( ip.type == IPv6 && netmask == 128 ) )
+ {
+ exactmatch = qtrue;
+ break;
+ }
+ }
+ continue;
}
for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
@@ -1615,7 +1647,7 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" );
return qfalse;
}
- else if( logmatches > 1 )
+ if( !ipmatch && logmatches > 1 )
{
ADMBP_begin();
ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" );
@@ -1660,7 +1692,7 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
admin_create_ban( ent,
g_admin_namelog[ logmatch ]->name[ 0 ],
g_admin_namelog[ logmatch ]->guid,
- g_admin_namelog[ logmatch ]->ip,
+ ipmatch ? search : g_admin_namelog[ logmatch ]->ip,
seconds, reason );
g_admin_namelog[ logmatch ]->banned = qtrue;
@@ -1670,30 +1702,6 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg )
else
admin_writeconfig();
- if( g_admin_namelog[ logmatch ]->slot == -1 )
- {
- // client is already disconnected so stop here
- AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7, "
- "duration: %s, reason: %s\n\"",
- g_admin_namelog[ logmatch ]->name[ 0 ],
- ( ent ) ? ent->client->pers.netname : "console",
- duration,
- ( *reason ) ? reason : "banned by admin" ) );
- return qtrue;
- }
-
- trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot,
- va( "disconnect \"You have been banned.\n"
- "admin:\n%s^7\nduration:\n%s\nreason:\n%s\"",
- ( ent ) ? ent->client->pers.netname : "console",
- duration,
- ( *reason ) ? reason : "banned by admin" ) );
-
- trap_DropClient( g_admin_namelog[ logmatch ]->slot,
- va( "has been banned by %s^7, duration: %s, reason: %s",
- ( ent ) ? ent->client->pers.netname : "console",
- duration,
- ( *reason ) ? reason : "banned by admin" ) );
return qtrue;
}
@@ -1744,10 +1752,12 @@ qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
char secs[ MAX_TOKEN_CHARS ];
char mode = '\0';
g_admin_ban_t *ban;
+ int mask = 0;
if( G_SayArgc() < 3 + skiparg )
{
- ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [duration] [reason]\n" );
+ ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [/mask] [duration] [reason]"
+ "\n" );
return qfalse;
}
G_SayArgv( 1 + skiparg, bs, sizeof( bs ) );
@@ -1766,6 +1776,19 @@ qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
return qfalse;
}
G_SayArgv( 2 + skiparg, secs, sizeof( secs ) );
+ if( secs[ 0 ] == '/' )
+ {
+ int max = strchr( ban->ip, ':' ) ? 128 : 32;
+ int min = ent ? max / 2 : 1;
+ mask = atoi( secs + 1 );
+ if( mask < min || mask > max )
+ {
+ ADMP( va( "^3!adjustban: ^7invalid netmask (%d is not one of %d-%d)\n",
+ mask, min, max ) );
+ return qfalse;
+ }
+ G_SayArgv( 3 + skiparg++, secs, sizeof( secs ) );
+ }
if( secs[ 0 ] == '+' || secs[ 0 ] == '-' )
mode = secs[ 0 ];
length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] );
@@ -1805,14 +1828,24 @@ qboolean G_admin_adjustban( gentity_t *ent, int skiparg )
G_admin_duration( ( expires ) ? expires - time : -1, duration,
sizeof( duration ) );
}
+ if( mask )
+ {
+ char *p = strchr( ban->ip, '/' );
+ if( !p )
+ p = ban->ip + strlen( ban->ip );
+ Com_sprintf( p, sizeof( ban->ip ) - ( p - ban->ip ), "/%d", mask );
+ }
reason = G_SayConcatArgs( 3 + skiparg );
if( *reason )
Q_strncpyz( ban->reason, reason, sizeof( ban->reason ) );
AP( va( "print \"^3!adjustban: ^7ban #%d for %s^7 has been updated by %s^7 "
- "%s%s%s%s%s\n\"",
+ "%s%s%s%s%s%s\n\"",
bnum,
ban->name,
( ent ) ? ent->client->pers.netname : "console",
+ ( mask ) ?
+ va( "netmask: /%d%s", mask,
+ ( length >= 0 || *reason ) ? ", " : "" ) : "",
( length >= 0 ) ? "duration: " : "",
duration,
( length >= 0 && *reason ) ? ", " : "",
@@ -2249,9 +2282,9 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
char date[ 11 ];
char *made;
char n1[ MAX_NAME_LENGTH * 2 ] = {""};
- qboolean numeric = qtrue;
- char *ip_match = NULL;
- int ip_match_len = 0;
+ qboolean ipmatch = qfalse;
+ addr_t ipa, ipb;
+ int neta, netb;
char name_match[ MAX_NAME_LENGTH ] = {""};
t = trap_RealTime( NULL );
@@ -2278,28 +2311,13 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
start = atoi( filter );
G_SayArgv( 2 + skiparg, filter, sizeof( filter ) );
}
- for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ )
- {
- if( !isdigit( filter[ i ] ) &&
- filter[ i ] != '.' && filter[ i ] != '-' )
- {
- numeric = qfalse;
- break;
- }
- }
- if( !numeric )
- {
- G_SanitiseString( filter, name_match, sizeof( name_match ) );
- }
- else if( strchr( filter, '.' ) )
- {
- ip_match = filter;
- ip_match_len = strlen(ip_match);
- }
else
{
- start = atoi( filter );
- filter[ 0 ] = '\0';
+ for( i = 0; filter[ i ] && isdigit( filter[ i ] ); i++ );
+ if( filter[ i ] )
+ G_SanitiseString( filter, name_match, sizeof( name_match ) );
+ else
+ start = atoi( filter );
}
// showbans 1 means start with ban 0
if( start > 0 )
@@ -2314,6 +2332,8 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
start = i + 1;
}
+ else
+ ipmatch = G_AddressParse( filter, &ipa, &neta );
}
if( start > max )
@@ -2327,15 +2347,20 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
if( g_admin_bans[ i ]->expires != 0 && g_admin_bans[ i ]->expires <= t )
continue;
- if( name_match[ 0 ] )
+ if( ipmatch )
+ {
+ if( !G_AddressParse( g_admin_bans[ i ]->ip, &ipb, &netb ) )
+ continue;
+ if( !G_AddressCompare( &ipa, &ipb, neta ) &&
+ !G_AddressCompare( &ipa, &ipb, netb ) )
+ continue;
+ }
+ else if( name_match[ 0 ] )
{
G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
if( !strstr( n1, name_match) )
continue;
}
- if( ip_match &&
- Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) )
- continue;
count++;
@@ -2354,15 +2379,20 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
if( g_admin_bans[ i ]->expires != 0 && g_admin_bans[ i ]->expires <= t )
continue;
- if( name_match[ 0 ] )
+ if( ipmatch )
+ {
+ if( !G_AddressParse( g_admin_bans[ i ]->ip, &ipb, &netb ) )
+ continue;
+ if( !G_AddressCompare( &ipa, &ipb, neta ) &&
+ !G_AddressCompare( &ipa, &ipb, netb ) )
+ continue;
+ }
+ else if( name_match[ 0 ] )
{
G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) );
if( !strstr( n1, name_match) )
continue;
}
- if( ip_match &&
- Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) )
- continue;
count++;
@@ -2401,11 +2431,11 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
g_admin_bans[ i ]->reason ) );
}
- if( name_match[ 0 ] || ip_match )
+ if( name_match[ 0 ] || ipmatch )
{
ADMBP( va( "^3!showbans:^7 found %d matching bans by %s. ",
count,
- ( ip_match ) ? "IP" : "name" ) );
+ ( ipmatch ) ? "IP" : "name" ) );
}
else
{
@@ -2419,8 +2449,8 @@ qboolean G_admin_showbans( gentity_t *ent, int skiparg )
if( i <= max )
ADMBP( va( " run !showbans %d%s%s to see more",
i + 1,
- ( filter[ 0 ] ) ? " " : "",
- ( filter[ 0 ] ) ? filter : "" ) );
+ ( name_match[ 0 ] ) ? " " : "",
+ ( name_match[ 0 ] ) ? filter : "" ) );
ADMBP( "\n" );
ADMBP_end();
return qtrue;
@@ -2764,28 +2794,42 @@ qboolean G_admin_namelog( gentity_t *ent, int skiparg )
char s2[ MAX_NAME_LENGTH ] = {""};
char n2[ MAX_NAME_LENGTH ] = {""};
int printed = 0;
+ addr_t a, b;
+ int mask = -1;
+ qboolean ipmatch = qfalse;
if( G_SayArgc() > 1 + skiparg )
{
G_SayArgv( 1 + skiparg, search, sizeof( search ) );
- G_SanitiseString( search, s2, sizeof( s2 ) );
+ ipmatch = G_AddressParse( search, &a, &mask );
+ if( !ipmatch )
+ G_SanitiseString( search, s2, sizeof( s2 ) );
}
ADMBP_begin();
for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ )
{
if( search[ 0 ] )
{
- for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
- g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
+ if( ipmatch )
{
- G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
- if( strstr( n2, s2 ) )
+ if( !G_AddressParse( g_admin_namelog[ i ]->ip, &b, NULL ) ||
+ !G_AddressCompare( &a, &b, mask ) )
+ continue;
+ }
+ else
+ {
+ for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&
+ g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ )
{
- break;
+ G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) );
+ if( strstr( n2, s2 ) )
+ {
+ break;
+ }
}
+ if( j == MAX_ADMIN_NAMELOG_NAMES || !g_admin_namelog[ i ]->name[ j ][ 0 ] )
+ continue;
}
- if( j == MAX_ADMIN_NAMELOG_NAMES || !g_admin_namelog[ i ]->name[ j ][ 0 ] )
- continue;
}
printed++;
ADMBP( va( "%s (*%s) %15s^7",
diff --git a/src/game/g_admin.h b/src/game/g_admin.h
index 5ac3c668..7917b2b3 100644
--- a/src/game/g_admin.h
+++ b/src/game/g_admin.h
@@ -109,7 +109,7 @@ typedef struct g_admin_ban
{
char name[ MAX_NAME_LENGTH ];
char guid[ 33 ];
- char ip[ 40 ];
+ char ip[ 44 ]; // big enough for IPv6 CIDR notation (without brackets)
char reason[ MAX_ADMIN_BAN_REASON ];
char made[ 18 ]; // big enough for strftime() %c
int expires;
@@ -136,7 +136,7 @@ typedef struct g_admin_namelog
}
g_admin_namelog_t;
-qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen );
+qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen );
qboolean G_admin_cmd_check( gentity_t *ent, qboolean say );
qboolean G_admin_readconfig( gentity_t *ent, int skiparg );
qboolean G_admin_permission( gentity_t *ent, const char *flag );
diff --git a/src/game/g_client.c b/src/game/g_client.c
index f0eaeeb0..78440563 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -1221,15 +1221,15 @@ char *ClientConnect( int clientNum, qboolean firstTime )
value = Info_ValueForKey( userinfo, "cl_guid" );
Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) );
+ value = Info_ValueForKey( userinfo, "ip" );
+ Q_strncpyz( client->pers.ip, value, sizeof( client->pers.ip ) );
+
// check for admin ban
- if( G_admin_ban_check( userinfo, reason, sizeof( reason ) ) )
+ if( G_admin_ban_check( ent, reason, sizeof( reason ) ) )
{
return va( "%s", reason );
}
- value = Info_ValueForKey( userinfo, "ip" );
- Q_strncpyz( client->pers.ip, value, sizeof( client->pers.ip ) );
-
// check for a password
value = Info_ValueForKey( userinfo, "password" );
diff --git a/src/game/g_local.h b/src/game/g_local.h
index 46a75c62..59d76f12 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -773,6 +773,20 @@ qboolean G_InPowerZone( gentity_t *self );
//
// g_utils.c
//
+#define ADDRLEN 16
+typedef struct
+{
+ enum
+ {
+ IPv4,
+ IPv6
+ } type;
+ byte addr[ ADDRLEN ];
+} addr_t;
+qboolean G_AddressParse( const char *str, addr_t *addr, int *netmask );
+qboolean G_AddressCompare( const addr_t *a, const addr_t *b, int netmask );
+qboolean G_AdrCmpStr( const char *a, const char *b );
+
int G_ParticleSystemIndex( char *name );
int G_ShaderIndex( char *name );
int G_ModelIndex( char *name );
diff --git a/src/game/g_utils.c b/src/game/g_utils.c
index 62fcc2a4..8b76704b 100644
--- a/src/game/g_utils.c
+++ b/src/game/g_utils.c
@@ -857,3 +857,192 @@ void G_CloseMenus( int clientNum )
Com_sprintf( buffer, 32, "serverclosemenus" );
trap_SendServerCommand( clientNum, buffer );
}
+
+
+/*
+===============
+G_AddressParse
+
+Make an IP address more usable
+===============
+*/
+static const char *addr4parse( const char *str, addr_t *addr )
+{
+ int i;
+ int octet = 0;
+ int num = 0;
+ memset( addr, 0, sizeof( addr_t ) );
+ addr->type = IPv4;
+ for( i = 0; octet < 4; i++ )
+ {
+ if( isdigit( str[ i ] ) )
+ num = num * 10 + str[ i ] - '0';
+ else
+ {
+ if( num < 0 || num > 255 )
+ return NULL;
+ addr->addr[ octet ] = (byte)num;
+ octet++;
+ if( str[ i ] != '.' || str[ i + 1 ] == '.' )
+ break;
+ num = 0;
+ }
+ }
+ if( octet < 1 )
+ return NULL;
+ return str + i;
+}
+
+static const char *addr6parse( const char *str, addr_t *addr )
+{
+ int i;
+ qboolean seen = qfalse;
+ /* keep track of the parts before and after the ::
+ it's either this or even uglier hacks */
+ byte a[ ADDRLEN ], b[ ADDRLEN ];
+ size_t before = 0, after = 0;
+ int num = 0;
+ /* 8 hexadectets unless :: is present */
+ for( i = 0; before + after <= 8; i++ )
+ {
+ //num = num << 4 | str[ i ] - '0';
+ if( isdigit( str[ i ] ) )
+ num = num * 16 + str[ i ] - '0';
+ else if( str[ i ] >= 'A' && str[ i ] <= 'F' )
+ num = num * 16 + 10 + str[ i ] - 'A';
+ else if( str[ i ] >= 'a' && str[ i ] <= 'f' )
+ num = num * 16 + 10 + str[ i ] - 'a';
+ else
+ {
+ if( num < 0 || num > 65535 )
+ return NULL;
+ if( i == 0 )
+ {
+ //
+ }
+ else if( seen ) // :: has been seen already
+ {
+ b[ after * 2 ] = num >> 8;
+ b[ after * 2 + 1 ] = num & 0xff;
+ after++;
+ }
+ else
+ {
+ a[ before * 2 ] = num >> 8;
+ a[ before * 2 + 1 ] = num & 0xff;
+ before++;
+ }
+ if( !str[ i ] )
+ break;
+ if( str[ i ] != ':' || i == 8 )
+ break;
+ if( str[ i + 1 ] == ':' )
+ {
+ // ::: or multiple ::
+ if( seen || str[ i + 2 ] == ':' )
+ break;
+ seen = qtrue;
+ i++;
+ }
+ else if( i == 0 ) // starts with : but not ::
+ return NULL;
+ num = 0;
+ }
+ }
+ if( seen )
+ {
+ // there have to be fewer than 8 hexadectets when :: is present
+ if( before + after == 8 )
+ return NULL;
+ }
+ else if( before + after < 8 ) // require exactly 8 hexadectets
+ return NULL;
+ memset( addr, 0, sizeof( addr_t ) );
+ addr->type = IPv6;
+ if( before )
+ memcpy( addr->addr, a, before * 2 );
+ if( after )
+ memcpy( addr->addr + ADDRLEN - 2 * after, b, after * 2 );
+ return str + i;
+}
+
+qboolean G_AddressParse( const char *str, addr_t *addr, int *netmask )
+{
+ const char *p;
+ int max;
+ if( strchr( str, ':' ) )
+ {
+ p = addr6parse( str, addr );
+ max = 128;
+ }
+ else
+ {
+ p = addr4parse( str, addr );
+ max = 32;
+ }
+ if( !p )
+ return qfalse;
+ if( *p == '/' )
+ {
+ if( netmask )
+ {
+ *netmask = atoi( p + 1 );
+ if( *netmask < 1 || *netmask > max )
+ *netmask = max;
+ }
+ }
+ else if( *p )
+ return qfalse;
+ return qtrue;
+}
+
+/*
+===============
+G_AddressCompare
+
+Based largely on NET_CompareBaseAdrMask from ioq3 revision 1557
+===============
+*/
+qboolean G_AddressCompare( const addr_t *a, const addr_t *b, int netmask )
+{
+ int i;
+ if( a->type != b->type )
+ return qfalse;
+ if( a->type == IPv4 )
+ {
+ if( netmask < 1 || netmask > 32 )
+ netmask = 32;
+ }
+ else if( a->type == IPv6 )
+ {
+ if( netmask < 1 || netmask > 128 )
+ netmask = 128;
+ }
+ for( i = 0; netmask > 7; i++, netmask -= 8 )
+ if( a->addr[ i ] != b->addr[ i ] )
+ return qfalse;
+ if( netmask )
+ {
+ netmask = ( ( 1 << netmask ) - 1 ) << ( 8 - netmask );
+ return ( a->addr[ i ] & netmask ) == ( b->addr[ i ] & netmask );
+ }
+ return qtrue;
+}
+
+/*
+===============
+G_AdrCmpStr
+
+The first argument may be in CIDR notation
+===============
+*/
+qboolean G_AdrCmpStr( const char *a, const char *b )
+{
+ int netmask = -1;
+ addr_t cmpa, cmpb;
+ if( !G_AddressParse( a, &cmpa, &netmask ) )
+ return qfalse;
+ if( !G_AddressParse( b, &cmpb, NULL ) )
+ return qfalse;
+ return G_AddressCompare( &cmpa, &cmpb, netmask );
+}