summaryrefslogtreecommitdiff
path: root/src/game
diff options
context:
space:
mode:
authorM. Kristall <mkpdev@gmail.com>2009-10-06 03:42:10 +0000
committerTim Angus <tim@ngus.net>2013-01-03 00:16:40 +0000
commit4c26128cbf19552bb3939e0b1b0a518a793e946f (patch)
tree0542e1cd5153b7e8bbfd00c3b3ea7620f7a78cd4 /src/game
parenta4478a4d40db8d11ce0ac54490565abeb65c3b5f (diff)
* (bug 4071) subnet bans
Allow banning and namelog searching by IP address in CIDR notation Banning a range of addresses will immediately kick all players in that range Admins with IMMUNITY flag can connect even if their IP address is banned admin.dat files relying on substring matching will have to be updated
Diffstat (limited to 'src/game')
-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 );
+}