From 116faaf0c94dac90b3d5c7a56ee3ddc22056ae55 Mon Sep 17 00:00:00 2001 From: TimeServ Date: Thu, 25 May 2006 04:47:03 +0000 Subject: [PATCH] bug fixes to ban logic, partial ipv4 addresses work for bans, ban with reasons (ex. ban 192.168.0.0/16 "I hate people on LANs"), addip/removeip/listip can use the same format as ban (and now work for ipv6/ipx), writeip removed and will be fixed/replaced later git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2295 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/common/net_wins.c | 91 ++++++++++++++--- engine/server/server.h | 8 ++ engine/server/sv_ccmds.c | 143 ++++++++++++++++++++++++-- engine/server/sv_main.c | 210 ++++++++------------------------------ engine/server/svq3_game.c | 14 ++- 5 files changed, 275 insertions(+), 191 deletions(-) diff --git a/engine/common/net_wins.c b/engine/common/net_wins.c index e2ef97551..03b31d72f 100644 --- a/engine/common/net_wins.c +++ b/engine/common/net_wins.c @@ -580,7 +580,7 @@ qboolean NET_StringToAdr (char *s, netadr_t *a) // (bits < 0 will always fill all bits) void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) { - int i; + unsigned int i; qbyte *n; memset (amask, 0, sizeof(*amask)); @@ -608,7 +608,8 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) // fill last bit if (i) { - i = (~((1 << i) - 1)) & 0xFF; + i = 8 - i; + i = 255 - ((1 << i) - 1); *n = i; } break; @@ -628,7 +629,8 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) // fill last bit if (i) { - i = (~((1 << i) - 1)) & 0xFF; + i = 8 - i; + i = 255 - ((1 << i) - 1); *n = i; } break; @@ -647,13 +649,62 @@ void NET_IntegerToMask (netadr_t *a, netadr_t *amask, int bits) // fill last bit if (i) { - i = (~((1 << i) - 1)) & 0xFF; + i = 8 - i; + i = 255 - ((1 << i) - 1); *n = i; } break; } } +// ParsePartialIPv4: check string to see if it is a partial IPv4 address and +// return bits to mask and set netadr_t or 0 if not an address +int ParsePartialIPv4(char *s, netadr_t *a) +{ + char *colon = NULL; + char *address = a->address.ip; + int bits = 8; + + if (!*s) + return 0; + + memset (a, 0, sizeof(*a)); + while (*s) + { + if (*s == ':') + { + if (colon) // only 1 colon + return 0; + colon = s + 1; + } + else if (*s == '.') + { + if (colon) // no colons before periods (probably invalid anyway) + return 0; + else if (bits >= 32) // only 32 bits in ipv4 + return 0; + else if (*(s+1) == '.') + return 0; + else if (*(s+1) == '\0') + break; // don't add more bits to the mask for x.x., etc + bits += 8; + address++; + } + else if (*s >= '0' && *s <= '9') + *address = ((*address)*10) + (*s-'0'); + else + return 0; // invalid character + + s++; + } + + a->type = NA_IP; + if (colon) + a->port = atoi(colon); + + return bits; +} + // NET_StringToAdrMasked: extension to NET_StringToAdr to handle IP addresses // with masks or integers representing the bit masks qboolean NET_StringToAdrMasked (char *s, netadr_t *a, netadr_t *amask) @@ -669,12 +720,12 @@ qboolean NET_StringToAdrMasked (char *s, netadr_t *a, netadr_t *amask) // we have a slash in the address so split and resolve separately char *c; - i = spoint - s; - if (i + 1 > sizeof(t)) + i = (int)(spoint - s) + 1; + if (i > sizeof(t)) i = sizeof(t); Q_strncpyz(t, s, i); - if (!NET_StringToAdr(t, a)) + if (!ParsePartialIPv4(t, a) && !NET_StringToAdr(t, a)) return false; spoint++; @@ -693,7 +744,7 @@ qboolean NET_StringToAdrMasked (char *s, netadr_t *a, netadr_t *amask) } if (c == NULL) // we have an address so resolve it and return - return NET_StringToAdr(spoint, amask); + return ParsePartialIPv4(spoint, amask) || NET_StringToAdr(spoint, amask); // otherwise generate mask for given bits i = atoi(spoint); @@ -702,13 +753,17 @@ qboolean NET_StringToAdrMasked (char *s, netadr_t *a, netadr_t *amask) else { // we don't have a slash, resolve and fill with a full mask - if (!NET_StringToAdr(s, a)) + i = ParsePartialIPv4(s, a); + if (!i && !NET_StringToAdr(s, a)) return false; memset (amask, 0, sizeof(*amask)); amask->type = a->type; - NET_IntegerToMask(a, amask, -1); + if (i) + NET_IntegerToMask(a, amask, i); + else + NET_IntegerToMask(a, amask, -1); } return true; @@ -774,7 +829,7 @@ int UniformMaskedBits(netadr_t mask) { int bits; int b; - int bs; + unsigned int bs; qboolean bitenc = false; switch (mask.type) @@ -788,7 +843,7 @@ int UniformMaskedBits(netadr_t mask) bitenc = true; else if (mask.address.ip[b]) { - bs = ~mask.address.ip[b]; + bs = (~mask.address.ip[b]) & 0xFF; while (bs) { if (bs & 1) @@ -818,7 +873,7 @@ int UniformMaskedBits(netadr_t mask) bitenc = true; else if (mask.address.ip6[b]) { - bs = ~mask.address.ip6[b]; + bs = (~mask.address.ip6[b]) & 0xFF; while (bs) { if (bs & 1) @@ -849,7 +904,7 @@ int UniformMaskedBits(netadr_t mask) bitenc = true; else if (mask.address.ipx[b]) { - bs = ~mask.address.ipx[b]; + bs = (~mask.address.ipx[b]) & 0xFF; while (bs) { if (bs & 1) @@ -887,7 +942,13 @@ char *NET_AdrToStringMasked (netadr_t a, netadr_t amask) if (i >= 0) sprintf(s, "%s/%i", NET_AdrToString(a), i); else - sprintf(s, "%s/%s", NET_AdrToString(a), NET_AdrToString(amask)); + { + // has to be done this way due to NET_AdrToString returning a + // static address + Q_strncatz(s, NET_AdrToString(a), sizeof(s)); + Q_strncatz(s, "/", sizeof(s)); + Q_strncatz(s, NET_AdrToString(amask), sizeof(s)); + } return s; } diff --git a/engine/server/server.h b/engine/server/server.h index 81be511d3..40ce2e77a 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -651,8 +651,15 @@ typedef struct bannedips_s { struct bannedips_s *next; netadr_t adr; netadr_t adrmask; + char reason[1]; } bannedips_t; +typedef struct filteredip_s { + struct filteredip_s *next; + netadr_t adr; + netadr_t adrmask; +} filteredips_t; + typedef enum { GT_PROGS, //q1, qw, h2 are similar enough that we consider it only one game mode. (We don't support the h2 protocol) GT_QUAKE2, //q2 servers run from a q2 game dll @@ -711,6 +718,7 @@ typedef struct challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting bannedips_t *bannedips; + filteredips_t *filteredips; char progsnames[MAX_PROGS][32]; progsnum_t progsnum[MAX_PROGS]; diff --git a/engine/server/sv_ccmds.c b/engine/server/sv_ccmds.c index ac89d381e..3d9dcafbc 100644 --- a/engine/server/sv_ccmds.c +++ b/engine/server/sv_ccmds.c @@ -638,13 +638,21 @@ void SV_BanName_f (void) { client_t *cl; int clnum=-1; + char *reason = NULL; + int reasonsize = 0; if (Cmd_Argc() < 2) { - Con_Printf("%s userid|nick\n", Cmd_Argv(0)); + Con_Printf("%s userid|nick [reason]\n", Cmd_Argv(0)); return; } + if (Cmd_Argc() > 2) + { + reason = Cmd_Argv(2); + reasonsize = strlen(reason); + } + while((cl = SV_GetClientForString(Cmd_Argv(1), &clnum))) if (cl) { @@ -656,13 +664,15 @@ void SV_BanName_f (void) continue; } - nb = Z_Malloc(sizeof(bannedips_t)); + nb = Z_Malloc(sizeof(bannedips_t)+reasonsize); nb->next = svs.bannedips; nb->adr = cl->netchan.remote_address; NET_IntegerToMask(&nb->adr, &nb->adrmask, -1); // fill mask if (*Cmd_Argv(2)) //explicit blocking of all ports of a client ip nb->adr.port = 0; svs.bannedips = nb; + if (reasonsize) + Q_strcpy(nb->reason, reason); SV_BroadcastTPrintf (PRINT_HIGH, STL_CLIENTWASBANNED, cl->name); // print directly, because the dropped client won't get the @@ -682,10 +692,12 @@ void SV_BanIP_f (void) int i; client_t *cl; bannedips_t *nb; + char *reason = NULL; + int reasonsize = 0; if (Cmd_Argc() < 2) { - Con_Printf("%s address/mask|adress/maskbits\n", Cmd_Argv(0)); + Con_Printf("%s address/mask|adress/maskbits [reason]\n", Cmd_Argv(0)); return; } @@ -701,6 +713,12 @@ void SV_BanIP_f (void) return; } + if (Cmd_Argc() > 2) + { + reason = Cmd_Argv(2); + reasonsize = strlen(reason); + } + // loop through clients and kick the ones that match for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++) { @@ -719,11 +737,58 @@ void SV_BanIP_f (void) } // add IP and mask to ban list - nb = Z_Malloc(sizeof(bannedips_t)); + nb = Z_Malloc(sizeof(bannedips_t)+reasonsize); nb->next = svs.bannedips; nb->adr = banadr; nb->adrmask = banmask; svs.bannedips = nb; + if (reasonsize) + Q_strcpy(nb->reason, reason); +} + +void SV_FilterIP_f (void) +{ + netadr_t banadr; + netadr_t banmask; + int i; + client_t *cl; + filteredips_t *nb; + extern cvar_t filterban; + + if (Cmd_Argc() < 2) + { + Con_Printf("%s address/mask|adress/maskbits\n", Cmd_Argv(0)); + return; + } + + if (!NET_StringToAdrMasked(Cmd_Argv(1), &banadr, &banmask)) + { + Con_Printf("invalid address or mask\n"); + return; + } + + if (NET_IsLoopBackAddress(banadr)) + { + Con_Printf("You're not allowed to filter loopback!\n"); + return; + } + + // loop through clients and kick the ones that match + for (i = 0, cl = svs.clients; i < sv.allocated_client_slots; i++, cl++) + { + if (cl->state<=cs_zombie) + continue; + + if (filterban.value && NET_CompareAdrMasked(cl->netchan.remote_address, banadr, banmask)) + SV_DropClient (cl); + } + + // add IP and mask to filter list + nb = Z_Malloc(sizeof(filteredips_t)); + nb->next = svs.filteredips; + nb->adr = banadr; + nb->adrmask = banmask; + svs.filteredips = nb; } void SV_BanList_f (void) @@ -733,7 +798,10 @@ void SV_BanList_f (void) while (nb) { - Con_Printf("%s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask)); + if (nb->reason[0]) + Con_Printf("%s, %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask), nb->reason); + else + Con_Printf("%s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask)); bancount++; nb = nb->next; } @@ -741,6 +809,21 @@ void SV_BanList_f (void) Con_Printf("%i total entries in ban list\n", bancount); } +void SV_FilterList_f (void) +{ + int filtercount = 0; + filteredips_t *nb = svs.filteredips; + + while (nb) + { + Con_Printf("%s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask)); + filtercount++; + nb = nb->next; + } + + Con_Printf("%i total entries in filter list\n", filtercount); +} + void SV_Unban_f (void) { qboolean all = false; @@ -766,7 +849,7 @@ void SV_Unban_f (void) while (nb) { nbnext = nb->next; - if (NET_CompareAdrMasked(nb->adr, unbanadr, unbanmask)) + if (all || NET_CompareAdrMasked(nb->adr, unbanadr, unbanmask)) { if (!all) Con_Printf("unbanned %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask)); @@ -779,6 +862,44 @@ void SV_Unban_f (void) } } +void SV_Unfilter_f (void) +{ + qboolean all = false; + filteredips_t *nb = svs.filteredips; + filteredips_t *nbnext; + netadr_t unbanadr = {0}; + netadr_t unbanmask = {0}; + + if (Cmd_Argc() < 2) + { + Con_Printf("%s address/mask|address/maskbits|all\n", Cmd_Argv(0)); + return; + } + + if (!Q_strcasecmp(Cmd_Argv(1), "all")) + all = true; + else if (!NET_StringToAdrMasked(Cmd_Argv(1), &unbanadr, &unbanmask)) + { + Con_Printf("invalid address or mask\n"); + return; + } + + while (nb) + { + nbnext = nb->next; + if (all || NET_CompareAdrMasked(nb->adr, unbanadr, unbanmask)) + { + if (!all) + Con_Printf("unfiltered %s\n", NET_AdrToStringMasked(nb->adr, nb->adrmask)); + if (svs.filteredips == nb) + svs.filteredips = nbnext; + Z_Free(nb); + } + + nb = nbnext; + } +} + void SV_ForceName_f (void) { client_t *cl; @@ -1807,6 +1928,16 @@ void SV_InitOperatorCommands (void) // Cmd_AddCommand ("ban", SV_BanName_f); Cmd_AddCommand ("status", SV_Status_f); + Cmd_AddCommand ("addip", SV_FilterIP_f); + Cmd_AddCommand ("removeip", SV_Unfilter_f); + Cmd_AddCommand ("listip", SV_FilterList_f); + +// Cmd_AddCommand ("filterip", SV_FilterIP_f); +// Cmd_AddCommand ("unfilter", SV_Unfilter_f); +// Cmd_AddCommand ("filterlist", SV_FilterList_f); + +// Cmd_AddCommand ("writeip", SV_WriteIP_f); + Cmd_AddCommand ("sv", SV_SendGameCommand_f); Cmd_AddCommand ("killserver", SV_KillServer_f); diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 4ad807f85..6b4a08e4b 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -196,6 +196,7 @@ void SV_FixupName(char *in, char *out); void SV_AcceptClient (netadr_t adr, int userid, char *userinfo); void Master_Shutdown (void); void PR_SetPlayerClass(client_t *cl, int classnum, qboolean fromqc); +bannedips_t *SV_BannedAddress (netadr_t *a); //============================================================================ @@ -1757,18 +1758,16 @@ client_t *SVC_DirectConnect(void) } } - { - bannedips_t *banip; - for (banip = svs.bannedips; banip; banip=banip->next) + bannedips_t *banip = SV_BannedAddress(&adr); + if (banip) { - if (NET_CompareAdrMasked(adr, banip->adr, banip->adrmask)) - { - SV_RejectMessage (protocol, "You were banned.\nContact the administrator to complain.\n"); - return NULL; - } + if (banip->reason[0]) + SV_RejectMessage (protocol, "You were banned.\nReason: %s\n", banip->reason); + else + SV_RejectMessage (protocol, "You were banned.\n"); + return NULL; } - //yay, a legit client who we havn't banned yet. } edictnum = (newcl-svs.clients)+1; @@ -2091,6 +2090,15 @@ void SVC_RemoteCommand (void) int i; char remaining[1024]; + { + bannedips_t *banip = SV_BannedAddress(&net_from); + if (banip) + { + Con_Printf ("Rcon from banned ip %s\n", NET_AdrToString (net_from)); + return; + } + } + if (!Rcon_Validate ()) { #ifdef SVRANKING @@ -2362,136 +2370,8 @@ If 0, then only addresses matching the list will be allowed. This lets you easi ============================================================================== */ - -typedef struct -{ - unsigned mask; - unsigned compare; -} ipfilter_t; - -#define MAX_IPFILTERS 1024 - -ipfilter_t ipfilters[MAX_IPFILTERS]; -int numipfilters; - cvar_t filterban = SCVAR("filterban", "1"); -/* -================= -StringToFilter -================= -*/ -qboolean StringToFilter (char *s, ipfilter_t *f) -{ - char num[128]; - int i, j; - qbyte b[4]; - qbyte m[4]; - - for (i=0 ; i<4 ; i++) - { - b[i] = 0; - m[i] = 0; - } - - for (i=0 ; i<4 ; i++) - { - if (*s < '0' || *s > '9') - { - Con_Printf ("Bad filter address: %s\n", s); - return false; - } - - j = 0; - while (*s >= '0' && *s <= '9') - { - num[j++] = *s++; - } - num[j] = 0; - b[i] = atoi(num); - if (b[i] != 0) - m[i] = 255; - - if (!*s) - break; - s++; - } - - f->mask = *(unsigned *)m; - f->compare = *(unsigned *)b; - - return true; -} - -/* -================= -SV_AddIP_f -================= -*/ -void SV_AddIP_f (void) -{ - int i; - - for (i=0 ; inext) + { + if (NET_CompareAdrMasked(*a, banip->adr, banip->adrmask)) return filterban.value; - + } return !filterban.value; } +// SV_BannedAdress, run through ban address list and return corresponding bannedips_t +// pointer, otherwise return NULL if not in the list +bannedips_t *SV_BannedAddress (netadr_t *a) +{ + bannedips_t *banip; + for (banip = svs.bannedips; banip; banip=banip->next) + { + if (NET_CompareAdrMasked(*a, banip->adr, banip->adrmask)) + return banip; + } + return NULL; +} + //send a network packet to a new non-connected client. //this is to combat tunneling void SV_OpenRoute_f(void) @@ -2598,11 +2478,8 @@ void SV_ReadPackets (void) good = false; while (SV_GetPacket ()) { - if (SV_FilterPacket ()) - { - SV_SendBan (); // tell them we aren't listening... + if (SV_FilterPacket (&net_from)) continue; - } // check for connectionless packet (0xffffffff) first if (*(int *)net_message.data == -1) @@ -3235,11 +3112,6 @@ void SV_InitLocal (void) Cvar_Register (&sv_nailhack, cvargroup_servercontrol); - Cmd_AddCommand ("addip", SV_AddIP_f); - Cmd_AddCommand ("removeip", SV_RemoveIP_f); - Cmd_AddCommand ("listip", SV_ListIP_f); - Cmd_AddCommand ("writeip", SV_WriteIP_f); - Cmd_AddCommand ("sv_impulse", SV_Impulse_f); Cmd_AddCommand ("openroute", SV_OpenRoute_f); diff --git a/engine/server/svq3_game.c b/engine/server/svq3_game.c index 0f0829df7..ac7a0f6d7 100644 --- a/engine/server/svq3_game.c +++ b/engine/server/svq3_game.c @@ -2911,6 +2911,7 @@ void SVQ3_HandleClient(void) SVQ3_ParseClientMessage(&svs.clients[i]); } +bannedips_t *SV_BannedAddress (netadr_t *a); void SVQ3_DirectConnect(void) //Actually connect the client, use up a slot, and let the gamecode know of it. { char *reason; @@ -2919,6 +2920,7 @@ void SVQ3_DirectConnect(void) //Actually connect the client, use up a slot, and int ret; int challenge; int qport; + bannedips_t *banip; if (net_message.cursize < 13) return; @@ -2934,7 +2936,17 @@ void SVQ3_DirectConnect(void) //Actually connect the client, use up a slot, and if (!cl) cl = SVQ3_FindEmptyPlayerSlot(); - if (!cl) + banip = SV_BannedAddress(&net_from); + + if (banip) + { + if (banip->reason[0]) + reason = banip->reason; + else + reason = "Banned."; + userinfo = NULL; + } + else if (!cl) { reason = "Server is full."; userinfo = NULL;