/*============================================================================== The Weapons Factory - IP Banning Code Original code by Red Barchetta Modified by Gregg Reno ==============================================================================*/ #include "g_local.h" #define BAN_FILE "listip.cfg" #define IPSEP '.' /* Comments I didn't add anything from newer CTF versions. My ban commands are "ban" and "unban" (with "bans" showing a list). It would be easy enough to rename them, though, if you rewrite them to behave like Zoid's. Here comes... I'm just going to paste the pieces of code into the message, since I need to cut up files to extract it. There are definitely better/shorter ways to do some of this stuff... it just isn't worth the time to make it efficient, since it is only called when you add an IP to the list. Also, I don't know if WF has an admin system (I would magine it does). If not, just rip out the admin checks in the commands (Adm_*). Also, you obviosly need to put calls to Adm_* in g_cmds.c (and g_svcmds.c if you want). Have fun... don't distribute this, and please send me whatever changes/improvements you make. // I use this to allow the file name to be changed. // put it in with the other cvar inits at game start if you want it // (you need to declare ban_file and gamedir to be extern cvar_t *'s ban_file = gi.cvar("ban_file", "", CVAR_USERINFO); */ int isspace(char c) { if (c == ' ' || c == 10 || c == 13) return 1; else return 0; } //The filter ban determines how the ip addresses are used. If //the filterban cvar = 1 (normal), then any player matching an //item in the ban list will be disallowed. If it is set to 0, then //all player that do NOT have an entry in the banlist will be //disallowed. This function simply changes the return code //based on the filter ban int CheckFilterBan(int retval) { if ((int)filterban->value == 0) { if (retval == 0) //swap return values return 1; else return 0; } else return retval; } // check to see if ip is on the banlist int IsBanned(char *ip) { ban_t *tmp = banlist; //gi.dprintf("Testing ban of %s\n",ip); while (tmp) { if (tmp->subnet) { if (!strncmp(tmp->ip, ip, tmp->subnet)) return CheckFilterBan(1); } else if (!strcmp(tmp->ip, ip)) { return CheckFilterBan(1); } // "else" tmp = tmp->next; } // if we're here, ip is not on the banlist return CheckFilterBan(0); } // check for properly-formatted IP address // NOTE you may need to hack this up to do Zoid-like subnet banning... int IsValidIP(char *ip) { char *term; // don't need to allocate, for whatever reason... // in fact, crashes if allocated and then later free()'d char *dot1, *dot2, *dot3, *end; if (!((dot1 = strchr(ip, IPSEP)) // check for first dot && (dot2 = strchr(dot1 + 1, IPSEP)) // check for second dot && (dot3 = strchr(dot2 + 1, IPSEP)) // check for third dot && !strchr(dot3 + 1, IPSEP))) { // shouldn't be more than 3 dots return 0; } // find the end of the address (this is the address of the NULL char) end = ip + strlen(ip); // check for more than 3 chars in any field // - 1's are to account for the 2 dots if ((dot1 - ip > 3) || (dot2 - dot1 - 1 > 3) || (dot3 - dot2 - 1 > 3) || (end - dot3 - 1 > 3)) { return 0; } // now check for fewer than 1 char in any field if ((dot1 - ip < 1) || (dot2 - dot1 - 1 < 1) || (dot3 - dot2 - 1 < 1) || (end - dot3 - 1 < 1)) { return 0; } // make sure fields contain only digits if (strtol(ip, &term, 10) > 255 || *term != '.') { return 0; } if (strtol(dot1 + 1, &term, 10) > 255 || *term != '.') { return 0; } if (strtol(dot2 + 1, &term, 10) > 255 || *term != '.') { return 0; } if (strtol(dot3 + 1, &term, 10) > 255 || *term != (char) NULL) { return 0; } // IP address is good return 1; } // read in the banlist // called void ReadBans() { FILE *banfile; ban_t *tmp; int len; int num3; char *dot3; char *bufptr; char *filename; char buf[81]; // need to tack the "game" onto the filename... // if (banfile = fopen(filename = va("%s/%s", *(gamedir->string) // ? gamedir->string : "baseq2", // ban_file->string[0] ? ban_file->string : BAN_FILE), "r")) { if (banfile = fopen(filename = va("%s/%s", *(gamedir->string) ? gamedir->string : "baseq2",BAN_FILE), "r")) { // read a line while (fgets(buf, 80, banfile)) { // don't consider the '\n' that may be in buf[] bufptr = buf; while (!isspace(*bufptr)) bufptr++; *bufptr = (char) NULL; if ((len = strlen(buf)) <= MAX_IP_LENGTH && IsValidIP(buf) && !IsBanned(buf)) { // add to head of list tmp = banlist; banlist = (ban_t *) malloc(sizeof(ban_t)); strcpy(banlist->ip, buf); banlist->next = tmp; // check for subnet ban dot3 = strrchr(buf, IPSEP); if (!(num3 = (int) strtol(dot3 + 1, (char **) NULL, 10))) // this is 1 + INDEX of the last '.' in the IP banlist->subnet = dot3 - buf + 1; else banlist->subnet = 0; // subnet is *not* banned gi.dprintf("Added %s to the banlist.\n", banlist->ip); } else { gi.dprintf("Invalid or already-banned IP \"%s\" read from file \"%s\".\n", buf, filename); } } fclose(banfile); } // else nothing to do! } // save the banlist to disk void WriteBans() { int i; FILE *banfile; ban_t *tmp; // need to tack the "game" onto the filename... // if (banfile = fopen(va("%s/%s", *(gamedir->string) ? gamedir->string // : "baseq2", // ban_file->string[0] ? ban_file->string : BAN_FILE), "w")) { if (banfile = fopen(va("%s/%s", *(gamedir->string) ? gamedir->string : "baseq2", BAN_FILE), "w")) { tmp = banlist; // write bans i = 0; while (tmp) { fprintf(banfile, "%s\n", tmp->ip); tmp = tmp->next; ++i; } fclose(banfile); } // else nothing to do! gi.dprintf("%d IP addresses saved.\n",i); } // test an IP against ban list void Adm_Test(char *cmd) { ban_t *tmp = banlist; if (!cmd) { gi.dprintf("usage: sv testip \n"); return; } //gi.dprintf("Testing ban of %s\n",ip); while (tmp) { if (tmp->subnet) { if (!strncmp(tmp->ip, cmd, tmp->subnet)) { gi.dprintf("%s banned. Matched subnet %s\n",cmd,tmp->ip); return; } } else if (!strcmp(tmp->ip, cmd)) { gi.dprintf("%s banned. Matched %s\n",cmd,tmp->ip); return; } // "else" tmp = tmp->next; } gi.dprintf("IP %s is NOT banned. \n",cmd); } // add an IP to the ban list void Adm_Ban(char *cmd) { int len; int no; int num3; int x = 0; char *junk; char *dot3; char *tmp_ip; // char *x; char to_ban[MAX_IP_LENGTH + 1]; ban_t *tmp; edict_t *cl_ent; if (!cmd) { gi.dprintf("usage: sv addip \n"); return; } len = strlen(cmd); //gi.dprintf("Cmd=[%s], Len = %d, minlen = %d\n",cmd, len,MIN_IP_LENGTH); if (len > MAX_IP_LENGTH) { gi.dprintf("Invalid IP address.\n"); return; } else if (len < MIN_IP_LENGTH) { // check if this is a valid player number no = (int) strtol(cmd, &junk, 10); if ((no < 0) || (no >= game.maxclients) || (*junk != (char) NULL)) { gi.dprintf("Invalid player number.\n"); return; } cl_ent = g_edicts + 1 + no; // get player if (!cl_ent->inuse) { gi.dprintf("Player number %d is not currently in use.\n", no); return; } // get player's IP tmp_ip = Info_ValueForKey(cl_ent->client->pers.userinfo, "ip"); // chop port number off of the IP address while (++x <= MAX_IP_LENGTH) { if (*(tmp_ip + x) == ':' || *(tmp_ip + x) == (char) NULL) break; } #if 0 // chop port number off of the IP address x = tmp_ip; while (x) { if (*(++x) == ':') { *x = (char) NULL; break; } } #endif // copy the IP to the ban string... // strncpy(to_ban, tmp_ip, MAX_IP_LENGTH); strncpy(to_ban, tmp_ip, x); // guarantee NULL terminate... // to_ban[MAX_IP_LENGTH] = (char) NULL; to_ban[x + 1] = (char) NULL; } // this is a normal IP (we hope) else if (IsValidIP(cmd)) { // copy the IP to the ban string... strncpy(to_ban, cmd, MAX_IP_LENGTH); // guarantee NULL terminate... to_ban[MAX_IP_LENGTH] = (char) NULL; } else { // invalid IP *and* player id. gi.dprintf("Invalid IP address.\n"); return; } // now do the actual ban if (IsBanned(to_ban)) { gi.dprintf("%s is already banned.\n", to_ban); } else { // insert at head of list tmp = banlist; banlist = (ban_t *) malloc(sizeof(ban_t)); // banlist->ip = (char *) malloc(len + 1); strcpy(banlist->ip, to_ban); banlist->next = tmp; // check for subnet ban // dot3 = strrchr(gi.argv(1 + sv), IPSEP); dot3 = strrchr(to_ban, IPSEP); if (!(num3 = (int) strtol(dot3 + 1, (char **) NULL, 10))) // this is 1 + INDEX of the last '.' in the IP banlist->subnet = dot3 - to_ban + 1; else banlist->subnet = 0; // subnet is *not* banned gi.dprintf("Added %s to the banlist.\n", banlist->ip); // make it permanent if possible WriteBans(); } } // remove an IP from the ban list void Adm_Unban(char *cmd) { int len; int sv = 0; // changes to 1 if first arg. is "sv" (server command) ban_t *tmp; ban_t *tmp_prev = (ban_t *) NULL; if (!cmd) { gi.dprintf("usage: sv removeip \n"); return; } if ((len = strlen(cmd)) > MAX_IP_LENGTH || len < MIN_IP_LENGTH) { gi.dprintf("Invalid IP address.\n"); } else { if (IsBanned(cmd)) { tmp = banlist; // check first individually so we can get "previous" if (strcmp(tmp->ip, cmd)) { tmp_prev = tmp; tmp = tmp->next; while (tmp && strcmp(tmp->ip, cmd)) { tmp_prev = tmp; tmp = tmp->next; } } if (tmp) { // just to be sure if (tmp_prev) // take tmp out of the list tmp_prev->next = tmp->next; else //this one is the first in the list banlist = tmp->next; // free(tmp->ip); free(tmp); gi.dprintf("Removed %s from the banlist.\n", cmd); // make it permanent if possible WriteBans(); } else // subnet is banned gi.dprintf("The entire subnet containing %s is banned. You need to remove the subnet ban to unban this IP.\n", cmd); } else { gi.dprintf("%s is not on the ban list.\n", cmd); } } } // show all bans void Adm_Bans(char *cmd) { int i = 0; int sv = 0; // changes to 1 if first arg. is "sv" (server command) ban_t *tmp; tmp = banlist; if (tmp) { gi.dprintf("-------------------\n"); gi.dprintf("BANNED IP ADDRESSES\n"); gi.dprintf("-------------------\n"); } while (tmp) { gi.dprintf("%s\n", tmp->ip); tmp = tmp->next; i++; } if (!i) gi.dprintf("No banned IPs.\n"); else gi.dprintf("%d total banned IPs.\n", i); } // kick *and* ban a player all in one shot. Prevents rejoins (obviously)... void Adm_KickBan(char *cmd) { int no; int sv = 0; // changes to 1 if first arg. is "sv" (server command) int x = 0; char *tmp_ip; char kbstr[32]; char *term; edict_t *cl_ent; if (!cmd) { gi.dprintf("usage: sv kickban \n"); return; } // check if this is a valid player number if ((no = (int) strtol(cmd, &term, 10)) < 0 || no >= game.maxclients || *term) { gi.dprintf("Invalid player number %s.\n", cmd); return; } cl_ent = g_edicts + 1 + no; // get player if (!cl_ent->inuse) { gi.dprintf("Player number %d is not currently in use.\n", no); return; } // get player's IP tmp_ip = Info_ValueForKey(cl_ent->client->pers.userinfo, "ip"); // chop port number off of the IP address while (++x <= MAX_IP_LENGTH) { if (*(tmp_ip + x) == ':' || *(tmp_ip + x) == (char) NULL) break; } // create ban command line-- need to use the server version strcpy(kbstr, "sv ban "); strncat(kbstr, tmp_ip, x); strcat(kbstr, "\n"); // ban! gi.AddCommandString(kbstr); // create the kick command line strcpy(kbstr, "kick "); strncat(kbstr, cmd, 3); strcat(kbstr, "\n"); //COMMENTED-- is this causing kb to crash the server? // // let the player know (sort of) // gi.dprintf("Don't come back!\n"); // kick! gi.AddCommandString(kbstr); }